Conversation
pull's rules sync used the team-repo item count for its 'Synced N rule(s)' message and totalSynced tally, regardless of whether any configured tool's rules/ directory actually existed. When no tool was installed, pullItem silently skipped every write (by design) but the log still reported success with the full count. - pull.ts: add hasInstalledRulesTarget(), a single per-pull check (isToolInstalled doesn't vary per rule) used to gate the success message and totalSynced increment; warn with actionable guidance when nothing was actually written. - rules.ts: pullAllRules() now accepts an optional force parameter; when true, pre-creates each configured tool's rules/ directory before the write loop so --force means 'sync regardless', as its own description says, without touching pullItem's signature (part of the ResourceHandler abstract contract). - known-agents.ts/init.ts/bootstrap.ts: renamed seedSelfModeToolDirs to seedEnabledAgentDirs and reused it from init's git and http paths. init --agent now seeds the declared tools' directories and runs one real pullForScope immediately, instead of leaving them empty until a SessionStart hook fires (which may never happen). - pull.ts: exported pullForScope so init.ts can trigger the initial sync directly. Fixes Tencent#574 Fixes Tencent#585
|
The That per-tool installed-directory test already exists seven times in
Every one walks If that helper takes the field as a parameter instead, something like Not blocking. Rules-only is a coherent scope if you would rather keep the diff tight for review, and the tests you added do not change either way. |
Merge: resolved conflict in src/pull.ts — kept hasInstalledRulesTarget
(this PR's fix) alongside upstream's cleanupTombstonedResources
refactor (independent additions, no logic overlap).
Generalize: per review feedback on this PR, the 'walk tools, check
isToolInstalled, skip if excluded' pattern behind hasInstalledRulesTarget
already existed 7 times in pull.ts. Replaced it and getInstalledResourceTargets
with two shared helpers:
- installedToolsFor(teamConfig, localConfig, field): tool ids with an
installed <field> directory (rules/skills/agents)
- hasInstalledTargetFor(...): boolean convenience wrapper
Applied the same phantom-success gate already built for rules to the
shared skills/agents write path, so 'no tool installed' now warns
instead of silently claiming success there too.
docs was checked and deliberately left out: DocsHandler.pullItem writes
unconditionally to a single fixed directory (fse.copy, which creates
the destination as needed) — there is no isToolInstalled gate or
per-tool skip to fix for docs.
Fixes Tencent#574
Fixes Tencent#585
Good catch generalized it. One adjustment: left docs out. Merge conflict with #598's |
|
One thing from reading the new commit, not blocking. The gate is a boolean over tools, and the write path is per tool. The list is already in hand, since -const hasTarget = await hasInstalledTargetFor(freshConfig, localConfig, type as 'skills' | 'agents');
-if (!hasTarget) {
+const installed = await installedToolsFor(freshConfig, localConfig, type as 'skills' | 'agents');
+if (installed.length === 0) {
log.warn(`… no installed tool directory found — nothing written…`);
} else {
…
- log.success(`[${scopeLabel}] Synced ${items.length} ${type}`);
+ log.success(`[${scopeLabel}] Synced ${items.length} ${type} → ${installed.join(', ')}`);That turns the count into a claim a reader can check, and it costs a variable. Whether a partially missing tool should warn as well as be omitted from that list is a design call I would leave to you — naming the tools that were written is already most of the value for someone reading their terminal. Unrelated, in case the file list raises an eyebrow: #599 (phase 1 of #598) rewrites |
Per review: hasInstalledTargetFor collapsed the installed-tools list to a boolean, so 'Synced N agents' still printed as a blanket success even when only some configured tools (e.g. claude, not codex) were installed — codex silently got nothing with no indication in the log. Switched the caller to installedToolsFor directly and appended the tool list to the success message: 'Synced 1 agents → claude'. Also fixes an unrelated missing closing brace introduced while editing this block (broke the whole file's parse — caught by tsc --noEmit before commit). Added a test with two configured tools, only one installed, asserting the message names the installed one and omits the other. Confirmed via git stash that it fails on the prior boolean-based code.
Fixed exactly as you showed One thing worth flagging: the same gap exists in the rules block ( Also caught and fixed an unrelated typo of my own while making this change a missing closing brace that broke the file's parse, caught by |
|
Yes, close rules here. It is the case #574 actually reported, so fixing the phantom count everywhere except that line would read oddly from the issue's side. Same shape as what you just did: - const hasTarget = await hasInstalledTargetFor(freshConfig, localConfig, 'rules');
- if (hasTarget) {
- log.success(`[${scopeLabel}] Synced ${items.length} rule(s)${skipped.length > 0 ? ` (skipped ${skipped.length} by tags)` : ''}`);
+ const installed = await installedToolsFor(freshConfig, localConfig, 'rules');
+ if (installed.length > 0) {
+ log.success(`[${scopeLabel}] Synced ${items.length} rule(s) → ${installed.join(', ')}${skipped.length > 0 ? ` (skipped ${skipped.length} by tags)` : ''}`);After that, Skills kept the opaque countThe gate landed for skills, the message did not. Skills logs through So with
function logSyncDetail(
type: ResourceType,
items: ResourceItem[],
existingNames: Set<string>,
verbose: boolean,
scopeLabel?: string,
skippedCount?: number,
+ installedTools?: string[],
): void {
const prefix = scopeLabel ? `[${scopeLabel}] ` : '';
+ const target = installedTools?.length ? ` → ${installedTools.join(', ')}` : '';Then One asymmetry to know aboutIn rules the check runs after |
|
Please resolve the P1 finding and conflicts. |
|
Findings
|
…#751) * fix(pull): gate the generic sync report on a tool that can receive it The generic branch of the sync loop reported the team repo's item count for every resource type: `Synced N skills` counted what the repo holds, not what landed. Skills are written per tool into that tool's own directory, and a brand-new member has none of them yet — the handler skips such a tool by design and only logs at debug. So the first pull after `init` printed a success while nothing was on disk, which is the phantom-success half of #585. `hasInstalledTargetFor` asks the same question `getInstalledResourceTargets` already asks, for one resource field, and the generic branch now gates its report on it. Docs need no gate: they are copied to the team's own docs directory, which the copy creates, so that report was already truthful. Only the report is gated. The writes still run, so a tool root created later — Cursor makes `.cursor/` on first launch — is filled by the next pull, and `pull --force` fills it now. Fixes #585 (the generic branch). #597 fixed the same shape for rules and left this one open; this closes it for skills. * fix(pull): gate the agents sync report on a receiving tool too The gate only covered skills, so the generic branch still printed `Synced N agents` when no installed tool could receive them — the same phantom-success shape #585 describes, one resource type over. AgentsHandler then resolves no destinations and writes nothing. `hasInstalledTargetFor` also duplicated the walk that `getInstalledResourceTargets` already performs. That function now takes an optional `field`, and the gate calls it, so reporting and writing share one resolver instead of two that can drift — an external HERMES_HOME or an unresolvable workspace no longer disagrees between them. Docs, rules and env never reach this branch; hooks and mcp have no tool-path field to probe, so they keep reporting unconditionally. Tests add the agent case the file's header already claimed: no tool directory suppresses the claim and nothing lands, and the claim returns once the directory exists. The suppression case is RED without the gate. --------- Co-authored-by: ydflow <314143294+ydflow@users.noreply.github.com>
Fixes #574
Fixes #585
What this fixes
pullandinit --agentboth had the same underlying issue: the reported outcome didn't reflect what was actually written to disk.pull(rules):pull.tsused the team-repo's rule count (scanTeamForPull's result) for theSynced N rule(s)message andtotalSyncedtally — regardless of whether any configured tool'srules/directory actually existed.RulesHandler.pullItem()correctly skips writing to an uninstalled tool (by design, perisToolInstalled's own docstring), but that skip is only logged atdebuglevel. So a user with no tool directories yet would see✔ Synced 7 rule(s)with zero files actually written — exactly #574's repro.init --agent: declaring--agent claude,codexnever created those tool directories or ran an initial sync — it only printed "will auto-sync on each session start," relying entirely on a SessionStart hook that may not fire before the user's first real session.--force: didn't bypassisToolInstalledanywhere, despite--force's own description ("sync regardless").Changes
pull.ts: addedhasInstalledRulesTarget()a single check per pull (not per-rule, sinceisToolInstalleddoesn't vary across rules in the same batch) that gates whether the success message/count fires. When no tool is installed, warns with actionable next steps instead of claiming success.rules.ts:pullAllRules()takes an optionalforceparam; when true, pre-creates each configured tool'srules/directory before the write loop, soisToolInstallednaturally passes and the existing write path proceeds — rather than special-casing a bypass insidepullItem(which isn't possible without changing theResourceHandlerabstract contract shared by every resource type).known-agents.ts/init.ts/bootstrap.ts: renamedseedSelfModeToolDirs→seedEnabledAgentDirs(per this issue's own suggestion) and reused it in bothinit's git and http paths.init --agentnow seeds the declared tools' directories and runs a realpullForScopeimmediately.pull.ts: exportedpullForScopesoinit.tscan call it directly for the initial sync.Scope
This covers rules specifically, matching this issue's repro and acceptance criteria. The same phantom-success pattern (team-repo count vs. actual-write count) exists in
pull.tsfor skills/docs/env/agents/team-culture/shared-instructions — same shape, different call sites. Flagging this as a known related gap rather than expanding this PR further; happy to follow up separately if useful.Verification
npx tsc --noEmitcleanlocal-agent.test.ts'suninstall_teamaitests, confirmed viagit stashto fail identically on unmodifiedmainunder full-suite load — unrelated to this change)init --agentsync, each confirmed to fail on unmodified code and pass with the fix (viagit stash/stash pop)teamai init <repo> --agent claudeend-to-end against a real throwaway GitHub repo and fresh directory — confirmed.claude/rules/*.mdactually lands on disk with no manualmkdir, matching the acceptance criteriaNot covered
--forcebypass is implemented for rules only, not the other resource types