devflow-kit 2.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +73 -0
- package/README.md +44 -19
- package/dist/agents/git.md +13 -15
- package/dist/cli/commands/ambient.js +160 -145
- package/dist/cli/commands/capture.js +29 -55
- package/dist/cli/commands/compliance.js +32 -61
- package/dist/cli/commands/context.js +17 -32
- package/dist/cli/commands/debug.js +65 -26
- package/dist/cli/commands/flags.js +3 -3
- package/dist/cli/commands/hud.js +34 -10
- package/dist/cli/commands/init-seed.js +40 -4
- package/dist/cli/commands/init.js +249 -271
- package/dist/cli/commands/install-report.js +10 -15
- package/dist/cli/commands/knowledge/index.js +1 -1
- package/dist/cli/commands/knowledge/toggle.js +11 -3
- package/dist/cli/commands/learning.js +52 -37
- package/dist/cli/commands/legacy-hooks.js +11 -14
- package/dist/cli/commands/memory.js +67 -78
- package/dist/cli/commands/proxy.js +23 -41
- package/dist/cli/commands/security.js +5 -13
- package/dist/cli/commands/skills.js +21 -3
- package/dist/cli/commands/tracker.js +100 -228
- package/dist/cli/commands/uninstall.js +343 -138
- package/dist/commands/bug-analysis.md +38 -12
- package/dist/commands/code-review.md +70 -21
- package/dist/commands/debug.md +37 -7
- package/dist/commands/dynamic-build.md +66 -17
- package/dist/commands/dynamic-plan.md +19 -8
- package/dist/commands/dynamic-profile.md +24 -10
- package/dist/commands/dynamic-tickets.md +22 -11
- package/dist/commands/explore.md +37 -7
- package/dist/commands/implement.md +96 -32
- package/dist/commands/plan.md +62 -19
- package/dist/commands/release.md +2 -2
- package/dist/commands/research.md +34 -8
- package/dist/commands/resolve.md +65 -17
- package/dist/commands/self-review.md +45 -9
- package/dist/core/compliance-compose.js +27 -27
- package/dist/core/evidence-policy.js +240 -24
- package/dist/core/feature-config.js +94 -25
- package/dist/core/feature-switch.js +1 -1
- package/dist/core/flags.js +30 -2
- package/dist/core/fs-atomic.js +27 -0
- package/dist/core/hook-log-dirs.js +104 -0
- package/dist/core/learning-tuning-config.js +5 -3
- package/dist/core/ledger-root.js +102 -0
- package/dist/core/manifest.js +6 -4
- package/dist/core/mds-variants.js +34 -97
- package/dist/core/migrations.js +49 -23
- package/dist/core/plugins.js +5 -4
- package/dist/core/project-paths.js +0 -17
- package/dist/core/same-location.js +25 -0
- package/dist/core/tracker.js +226 -139
- package/dist/hud/components/config-counts.js +15 -4
- package/dist/hud/components/learning-counts.js +14 -0
- package/dist/hud/config.js +2 -1
- package/dist/hud/cost-history.js +2 -4
- package/dist/hud/git.js +52 -7
- package/dist/hud/index.js +7 -9
- package/dist/skills/git/references/pr/check-merge-readiness.md +1 -1
- package/dist/skills/git/references/pr/ensure-pr-ready.md +1 -1
- package/dist/skills/git/references/pr/update-pr-evidence.md +1 -1
- package/dist/skills/git/references/tracker/_mcp.md +1 -1
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/github/manage-debt.md +3 -3
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/jira/manage-debt.md +1 -1
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +1 -1
- package/dist/skills/git/references/tracker/jira/setup-task.md +1 -1
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +1 -1
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +1 -1
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +2 -2
- package/dist/skills/git/references/tracker/linear/manage-debt.md +1 -1
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +1 -1
- package/dist/skills/git/references/tracker/linear/setup-task.md +1 -1
- package/dist/targets/claude-code/claude-paths.js +59 -57
- package/dist/targets/claude-code/compliance-install.js +49 -65
- package/dist/targets/claude-code/hooks.js +108 -3
- package/dist/targets/claude-code/installer.js +30 -57
- package/dist/targets/claude-code/post-install.js +232 -139
- package/dist/targets/claude-code/tracker-install.js +38 -65
- package/package.json +5 -4
- package/src/assets/agents/code.md +4 -3
- package/src/assets/agents/design.md +1 -0
- package/src/assets/agents/git.mds +55 -57
- package/src/assets/agents/knowledge.md +2 -2
- package/src/assets/agents/review.md +3 -1
- package/src/assets/agents/tracker.md +37 -30
- package/src/assets/commands/_partials/_compliance.mds +19 -1
- package/src/assets/commands/_partials/_decisions.mds +15 -3
- package/src/assets/commands/_partials/_docs_root.mds +35 -0
- package/src/assets/commands/_partials/_engine.mds +2 -2
- package/src/assets/commands/_partials/_evidence_policy.mds +3 -3
- package/src/assets/commands/_partials/_factory.mds +1 -1
- package/src/assets/commands/_partials/_knowledge.mds +27 -9
- package/src/assets/commands/_partials/_plan_contract.mds +2 -2
- package/src/assets/commands/_partials/_preamble.mds +1 -1
- package/src/assets/commands/_partials/_publication.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +28 -0
- package/src/assets/commands/_partials/_ticket_template.mds +3 -3
- package/src/assets/commands/_partials/_tracker.mds +4 -4
- package/src/assets/commands/_partials/_wave.mds +4 -4
- package/src/assets/commands/bug-analysis.mds +19 -17
- package/src/assets/commands/code-review.mds +39 -33
- package/src/assets/commands/debug.mds +4 -5
- package/src/assets/commands/dynamic-build.mds +75 -53
- package/src/assets/commands/dynamic-plan.mds +20 -15
- package/src/assets/commands/dynamic-profile.mds +24 -11
- package/src/assets/commands/dynamic-tickets.mds +25 -20
- package/src/assets/commands/explore.mds +4 -5
- package/src/assets/commands/implement.mds +58 -45
- package/src/assets/commands/plan.mds +34 -29
- package/src/assets/commands/release.md +2 -2
- package/src/assets/commands/research.mds +11 -9
- package/src/assets/commands/resolve.mds +41 -39
- package/src/assets/commands/self-review.mds +24 -25
- package/src/assets/mds/git/_pr.mds +61 -61
- package/src/assets/mds/git/_references.mds +19 -19
- package/src/assets/mds/tracker/_common.mds +8 -8
- package/src/assets/mds/tracker/_github.mds +71 -71
- package/src/assets/mds/tracker/_jira.mds +74 -74
- package/src/assets/mds/tracker/_linear.mds +75 -75
- package/src/assets/mds/tracker/_mcp.mds +23 -17
- package/src/assets/scripts/hooks/background-memory-update +35 -19
- package/src/assets/scripts/hooks/capture-prompt +18 -12
- package/src/assets/scripts/hooks/capture-question +18 -12
- package/src/assets/scripts/hooks/capture-turn +27 -17
- package/src/assets/scripts/hooks/debug-trace +11 -6
- package/src/assets/scripts/hooks/ensure-devflow-init +33 -6
- package/src/assets/scripts/hooks/ensure-proxy +9 -8
- package/src/assets/scripts/hooks/ensure-root-gitignore +111 -36
- package/src/assets/scripts/hooks/git-marker +48 -0
- package/src/assets/scripts/hooks/json-helper.cjs +6 -1
- package/src/assets/scripts/hooks/lib/project-paths.cjs +0 -19
- package/src/assets/scripts/hooks/log-paths +80 -0
- package/src/assets/scripts/hooks/memory-worker +17 -15
- package/src/assets/scripts/hooks/pre-compact-memory +41 -16
- package/src/assets/scripts/hooks/queue-append +104 -30
- package/src/assets/scripts/hooks/resolve-project-root +101 -7
- package/src/assets/scripts/hooks/session-start-context +289 -122
- package/src/assets/scripts/hooks/session-start-memory +35 -16
- package/src/assets/scripts/lib/project-config.cjs +633 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +300 -220
- package/src/assets/scripts/resolve-settings.cjs +1054 -0
- package/src/assets/scripts/verify-evidence.cjs +1 -1
- package/src/assets/skills/compliance/SKILL.md +2 -2
- package/src/assets/skills/docs-framework/SKILL.md +6 -7
- package/src/assets/skills/docs-framework/references/patterns.md +10 -17
- package/src/assets/skills/gap-analysis/SKILL.md +2 -2
- package/src/assets/skills/git/references/github-api.md +9 -9
- package/src/assets/skills/git/references/patterns.md +1 -1
- package/src/assets/skills/worktree-support/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +29 -0
- package/src/targets/claude-code/templates/managed-settings.json +25 -9
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* devflow tracker — Show or set the issue tracker provider.
|
|
2
|
+
* devflow tracker — Show or set the machine's issue tracker provider.
|
|
3
3
|
*
|
|
4
4
|
* D-TRACKER-PAIR [DR-25]: `src/core/tracker.ts` (domain) +
|
|
5
5
|
* `src/cli/commands/tracker.ts` (CLI) mirrors the `compliance.ts` pair
|
|
@@ -9,103 +9,55 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Applies ADR-013: CLI-layer module; the provider domain, the strict parser and
|
|
11
11
|
* the ~/.devflow file lifecycle all live in src/core/tracker.ts.
|
|
12
|
-
* The manifest
|
|
13
|
-
*
|
|
14
|
-
* .
|
|
12
|
+
* The manifest holds the MACHINE default. A repository may select its own
|
|
13
|
+
* provider in its committed `.devflow/project.json` (resolved by
|
|
14
|
+
* resolve-settings.cjs); `--status` names it on an `Effective:` line when one
|
|
15
|
+
* does, and `--set` never touches it.
|
|
15
16
|
* Avoids PF-015: --set converges the sentinel in BOTH directions, so flipping
|
|
16
17
|
* back to github removes what flipping away wrote.
|
|
17
|
-
* Avoids PF-009: a failed
|
|
18
|
+
* Avoids PF-009: a failed re-arm or sentinel step warns, it never aborts.
|
|
18
19
|
*/
|
|
19
20
|
import { Command } from 'commander';
|
|
20
21
|
import { promises as fs } from 'fs';
|
|
21
22
|
import * as path from 'path';
|
|
22
23
|
import * as p from '@clack/prompts';
|
|
23
24
|
import color from 'picocolors';
|
|
24
|
-
import { DEFAULT_TRACKER_PROVIDER, TRACKER_ATTEMPTS_MAX, TRACKER_PROVIDERS, applyTrackerSentinel, describeTrackerValue, parseTrackerId,
|
|
25
|
+
import { DEFAULT_TRACKER_PROVIDER, TRACKER_ATTEMPTS_MAX, TRACKER_CONVENTIONS_READ_BYTES, TRACKER_PROVIDERS, applyTrackerSentinel, describeTrackerValue, parseTrackerFrontmatter, parseTrackerId, readBoundedHead, rearmTrackerInference, trackerConventionsPath, } from '../../core/tracker.js';
|
|
25
26
|
import { readManifest, syncManifestFeature } from '../../core/manifest.js';
|
|
27
|
+
import { loadSettingsModule, personalConfigTrackedWarning, repoTrackerSelection } from '../../core/evidence-policy.js';
|
|
26
28
|
import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
|
|
27
|
-
import { overlayInstalledReferences, overlayUnitLabel } from '../../targets/claude-code/installer.js';
|
|
28
|
-
import { convergeTrackerArtifacts } from '../../targets/claude-code/tracker-install.js';
|
|
29
|
-
import { describeOverlayFailureState } from './install-report.js';
|
|
30
29
|
import { SKILL_REFS_SKILL_NAME, installedReferenceManifest } from '../../core/mds-variants.js';
|
|
31
30
|
import { prefixSkillName } from '../../core/plugins.js';
|
|
32
|
-
/** How many leading lines of tracker.md are scanned for frontmatter. */
|
|
33
|
-
const PROVENANCE_SCAN_LINES = 40;
|
|
34
31
|
/**
|
|
35
|
-
*
|
|
32
|
+
* Read the provenance header of `~/.devflow/tracker/{provider}.md`.
|
|
36
33
|
*
|
|
37
|
-
*
|
|
38
|
-
* limit truncates the resulting array once the whole file is already in memory.
|
|
39
|
-
* tracker.md is hand-editable and machine-wide, so its size is not devflow's to
|
|
40
|
-
* assume — this is the same bound the Tracker agent writes to and the Git agent
|
|
41
|
-
* loads, enforced at the one TypeScript reader (avoids PF-023: a bound is only
|
|
42
|
-
* real at the sink, and a file round-trip launders the writer's promise).
|
|
43
|
-
*/
|
|
44
|
-
const PROVENANCE_READ_BYTES = 8000;
|
|
45
|
-
/**
|
|
46
|
-
* Read at most `limit` bytes from the head of a file.
|
|
47
|
-
*
|
|
48
|
-
* `undefined` for an absent, unreadable or non-file path — the caller reports
|
|
49
|
-
* that as `absent` (PF-014: never throws).
|
|
50
|
-
*/
|
|
51
|
-
async function readBoundedHead(filePath, limit) {
|
|
52
|
-
let handle;
|
|
53
|
-
try {
|
|
54
|
-
handle = await fs.open(filePath, 'r');
|
|
55
|
-
const buffer = Buffer.alloc(limit);
|
|
56
|
-
const { bytesRead } = await handle.read(buffer, 0, limit, 0);
|
|
57
|
-
return buffer.subarray(0, bytesRead).toString('utf-8');
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
return undefined;
|
|
61
|
-
}
|
|
62
|
-
finally {
|
|
63
|
-
await handle?.close().catch(() => undefined);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Read the provenance header of `~/.devflow/tracker.md`.
|
|
68
|
-
*
|
|
69
|
-
* Never throws (PF-014): an absent, unreadable, or directory path is `absent`.
|
|
34
|
+
* Never throws (PF-014): an absent, unreadable, or non-regular path is `absent`.
|
|
70
35
|
* Only the bounded head of the file is read and only the leading frontmatter
|
|
71
|
-
* block is scanned
|
|
72
|
-
*
|
|
73
|
-
*
|
|
36
|
+
* block is scanned — through the one frontmatter parser the migration also uses —
|
|
37
|
+
* and nothing read here is trusted: every value is rendered through
|
|
38
|
+
* `describeTrackerValue`, because the file is hand-editable and machine-wide, so
|
|
39
|
+
* its content is third-party input at every sink.
|
|
74
40
|
*/
|
|
75
|
-
export async function readTrackerProvenance(devflowDir) {
|
|
76
|
-
const head = await readBoundedHead(trackerConventionsPath(devflowDir),
|
|
41
|
+
export async function readTrackerProvenance(devflowDir, provider) {
|
|
42
|
+
const head = await readBoundedHead(trackerConventionsPath(devflowDir, provider), TRACKER_CONVENTIONS_READ_BYTES);
|
|
77
43
|
if (head === undefined)
|
|
78
44
|
return { kind: 'absent' };
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
return { kind: 'present' };
|
|
82
|
-
let provider;
|
|
83
|
-
let inferredFrom;
|
|
84
|
-
for (const line of lines.slice(1)) {
|
|
85
|
-
if (line.trim() === '---')
|
|
86
|
-
break;
|
|
87
|
-
const match = /^([A-Za-z-]+):\s*(.*)$/.exec(line);
|
|
88
|
-
if (match === null)
|
|
89
|
-
continue;
|
|
90
|
-
if (match[1] === 'provider' && provider === undefined)
|
|
91
|
-
provider = match[2].trim();
|
|
92
|
-
if (match[1] === 'inferred-from' && inferredFrom === undefined)
|
|
93
|
-
inferredFrom = match[2].trim();
|
|
94
|
-
}
|
|
95
|
-
return { kind: 'present', provider, inferredFrom };
|
|
45
|
+
const { provider: named, inferredFrom } = parseTrackerFrontmatter(head);
|
|
46
|
+
return { kind: 'present', provider: named, inferredFrom };
|
|
96
47
|
}
|
|
97
48
|
/**
|
|
98
|
-
* Count the installed tracker mechanics
|
|
49
|
+
* Count the installed tracker mechanics.
|
|
99
50
|
*
|
|
100
|
-
* Counts what is PRESENT against the manifest
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* to
|
|
51
|
+
* Counts what is PRESENT against the install manifest — every provider's, since
|
|
52
|
+
* every install carries them all (D-INSTALL-ALL-PROVIDERS) — rather than listing
|
|
53
|
+
* the directory: the question a user asks `--status` is "can the agent load what
|
|
54
|
+
* it is told to load?", and a stray file in the tree is not an answer to it.
|
|
55
|
+
* Never throws (PF-014).
|
|
104
56
|
*/
|
|
105
|
-
export async function readTrackerMechanics(claudeDir
|
|
57
|
+
export async function readTrackerMechanics(claudeDir) {
|
|
106
58
|
const root = path.join(claudeDir, 'skills', prefixSkillName(SKILL_REFS_SKILL_NAME), 'references');
|
|
107
59
|
let present = 0;
|
|
108
|
-
for (const rel of installedReferenceManifest(
|
|
60
|
+
for (const rel of installedReferenceManifest()) {
|
|
109
61
|
try {
|
|
110
62
|
await fs.access(path.join(root, ...rel.split('/')));
|
|
111
63
|
present++;
|
|
@@ -150,168 +102,88 @@ export function formatTrackerProvenance(provenance) {
|
|
|
150
102
|
}
|
|
151
103
|
return parts.join(' — ');
|
|
152
104
|
}
|
|
105
|
+
/** Read the conventions `--status` reports for `provider`. Never throws: see readTrackerProvenance. */
|
|
106
|
+
export async function readTrackerStatusConventions(devflowDir, provider) {
|
|
107
|
+
if (provider === DEFAULT_TRACKER_PROVIDER)
|
|
108
|
+
return { kind: 'none' };
|
|
109
|
+
return {
|
|
110
|
+
kind: 'learned',
|
|
111
|
+
file: trackerConventionsPath(devflowDir, provider),
|
|
112
|
+
provenance: await readTrackerProvenance(devflowDir, provider),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* The `--status` note's lines. Pure.
|
|
117
|
+
*
|
|
118
|
+
* The `Effective:` line appears ONLY when a repository layer selects the tracker
|
|
119
|
+
* (`selection` non-null) — `Effective: jira (project)` — so a machine whose own
|
|
120
|
+
* provider decides prints the same five labels it always has. The conventions
|
|
121
|
+
* lines describe the provider in effect HERE, which is the one whose file a
|
|
122
|
+
* session in this repository learns and a Git spawn in it reads.
|
|
123
|
+
*/
|
|
124
|
+
export function formatTrackerStatus(input) {
|
|
125
|
+
const providerLabel = input.machine === DEFAULT_TRACKER_PROVIDER
|
|
126
|
+
? `${color.green(input.machine)} ${color.dim('(default)')}`
|
|
127
|
+
: color.green(input.machine);
|
|
128
|
+
const lines = [`Provider: ${providerLabel}`];
|
|
129
|
+
if (input.selection !== null) {
|
|
130
|
+
lines.push(`Effective: ${color.green(input.selection.provider)} (${input.selection.source})`);
|
|
131
|
+
}
|
|
132
|
+
lines.push(...(input.conventions.kind === 'none'
|
|
133
|
+
? ['Conventions: none (GitHub needs no learned conventions)', 'File: none']
|
|
134
|
+
: [`Conventions: ${formatTrackerProvenance(input.conventions.provenance)}`, `File: ${input.conventions.file}`]), `Mechanics: ${formatTrackerMechanics(input.mechanics)}`, `Inference: ${input.inference}`);
|
|
135
|
+
return lines.join('\n');
|
|
136
|
+
}
|
|
153
137
|
/** The real adapter — the ONE binding of each operation into the `--set` path. */
|
|
154
138
|
export function buildTrackerSetIO() {
|
|
155
139
|
return {
|
|
156
|
-
gitSkillInstalled: async (claudeDir) => {
|
|
157
|
-
try {
|
|
158
|
-
await fs.access(path.join(claudeDir, 'skills', prefixSkillName(SKILL_REFS_SKILL_NAME), 'SKILL.md'));
|
|
159
|
-
return true;
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
return false;
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
overlayReferences: (claudeDir, provider, warn) => overlayInstalledReferences({ claudeDir, provider, warn }),
|
|
166
|
-
renameStaleConventions: renameStaleTrackerConventions,
|
|
167
140
|
syncManifest: (devflowDir, state) => syncManifestFeature(devflowDir, 'tracker', state),
|
|
168
|
-
convergeArtifacts: (claudeDir, provider, warn) => convergeTrackerArtifacts({ claudeDir, provider, warn }),
|
|
169
141
|
rearmInference: rearmTrackerInference,
|
|
170
142
|
applySentinel: applyTrackerSentinel,
|
|
171
143
|
};
|
|
172
144
|
}
|
|
173
145
|
/**
|
|
174
|
-
*
|
|
146
|
+
* Select the machine's default tracker provider.
|
|
175
147
|
*
|
|
176
|
-
* D-TRACKER-CONVERGE-SET:
|
|
177
|
-
* same as `devflow init`'s — the two are different call paths obeying one
|
|
178
|
-
* principle, and forcing them through a shared signature would hide the
|
|
179
|
-
* reordering rather than document it.
|
|
148
|
+
* D-TRACKER-CONVERGE-SET: `--set` writes exactly three things, in this order —
|
|
180
149
|
*
|
|
181
|
-
* 1.
|
|
182
|
-
* 2.
|
|
183
|
-
* 3.
|
|
184
|
-
* 4. persist the manifest
|
|
185
|
-
* 5. converge the Tracker AGENT file
|
|
186
|
-
* 6. re-arm the attempt counter
|
|
187
|
-
* 7. converge the sentinel (write gated on 5; removal unconditional)
|
|
150
|
+
* 1. the manifest (the machine default; what every other step names)
|
|
151
|
+
* 2. the attempt counters (re-armed, so the new provider gets its five tries)
|
|
152
|
+
* 3. the sentinel (the provider's name, or removed for github)
|
|
188
153
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
* never get ahead of the manifest that records it.
|
|
154
|
+
* and nothing else. Every provider's mechanics and the Tracker agent are already
|
|
155
|
+
* installed (D-INSTALL-ALL-PROVIDERS), so a selection change has no reference tree
|
|
156
|
+
* to swap and no agent to add or remove; and each provider keeps its own
|
|
157
|
+
* conventions file (D-TRACKER-PER-PROVIDER-CONVENTIONS), so there is nothing to
|
|
158
|
+
* move aside. The sentinel follows the manifest because it ADVERTISES the value
|
|
159
|
+
* the manifest records, and must never get ahead of it.
|
|
196
160
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* conventions unchanged. What the overlay branch cannot claim is that nothing
|
|
200
|
-
* moved at all: the overlay is atomic PER UNIT, so units that succeeded are
|
|
201
|
-
* converged and only the failed ones are reported (design review H1). Saying
|
|
202
|
-
* "nothing else changed" would be the more comfortable sentence and the false
|
|
203
|
-
* one.
|
|
204
|
-
*
|
|
205
|
-
* Never throws, except where its callee does: an absent generated tree is a
|
|
206
|
-
* build artifact that was never produced, and the CLI turns that into exit 1
|
|
207
|
-
* with the build command named — asymmetric against the warn-only rename,
|
|
208
|
-
* re-arm and sentinel steps, because those degrade a working install while an
|
|
209
|
-
* absent tree means there is nothing to install at all.
|
|
161
|
+
* Never throws, except where the manifest writer does. The re-arm and sentinel
|
|
162
|
+
* steps warn rather than abort: each degrades a working install, never breaks it.
|
|
210
163
|
*/
|
|
211
164
|
export async function runTrackerSet(opts) {
|
|
212
|
-
const { devflowDir,
|
|
165
|
+
const { devflowDir, current, requested, io } = opts;
|
|
213
166
|
const messages = [];
|
|
214
|
-
const abort = (text) => {
|
|
215
|
-
messages.push({ level: 'error', text });
|
|
216
|
-
return { exitCode: 1, provider: current.provider, messages };
|
|
217
|
-
};
|
|
218
|
-
// 1. Without devflow:git there is no references/ directory to converge into,
|
|
219
|
-
// and creating one would leave an invisible husk under a skill that does
|
|
220
|
-
// not exist — a directory no sweep looks inside because no skill claims it.
|
|
221
|
-
if (!await io.gitSkillInstalled(claudeDir)) {
|
|
222
|
-
return abort(`devflow:git is not installed — run devflow init --tracker ${requested}. ` +
|
|
223
|
-
`The manifest, sentinel and conventions file are unchanged.`);
|
|
224
|
-
}
|
|
225
|
-
// 2. The overlay. Runs even when the provider is unchanged, so a `--set` that
|
|
226
|
-
// repeats the current selection self-heals a damaged subtree instead of
|
|
227
|
-
// early-returning on an equality check that proves nothing about disk.
|
|
228
|
-
const overlayWarnings = [];
|
|
229
|
-
let overlay;
|
|
230
|
-
try {
|
|
231
|
-
overlay = await io.overlayReferences(claudeDir, requested, (msg) => overlayWarnings.push(msg));
|
|
232
|
-
}
|
|
233
|
-
catch (error) {
|
|
234
|
-
return abort(`Tracker: not changed — ${requested} assets could not be installed. ` +
|
|
235
|
-
`${error instanceof Error ? error.message : String(error)}`);
|
|
236
|
-
}
|
|
237
|
-
for (const text of overlayWarnings)
|
|
238
|
-
messages.push({ level: 'warn', text });
|
|
239
|
-
if (overlay.overlayFailures.length > 0) {
|
|
240
|
-
for (const failure of overlay.overlayFailures) {
|
|
241
|
-
messages.push({
|
|
242
|
-
level: 'warn',
|
|
243
|
-
text: `${overlayUnitLabel(failure.unit)}: ${failure.error} — ` +
|
|
244
|
-
`${describeOverlayFailureState(failure.state)}`,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
return abort(`Tracker: not changed — ${requested} assets could not be installed. The manifest, ` +
|
|
248
|
-
`sentinel and conventions file are unchanged; the overlay is atomic per unit, so the ` +
|
|
249
|
-
`units that succeeded are converged and the ${overlay.overlayFailures.length} that failed ` +
|
|
250
|
-
`are reported above.`);
|
|
251
|
-
}
|
|
252
|
-
// 3. A conventions file inferred for the previous provider is stale the moment
|
|
253
|
-
// the provider changes — move it aside so it can never be silently
|
|
254
|
-
// authoritative, and so the reader-side mismatch guard has nothing to fight.
|
|
255
|
-
const transition = await io.renameStaleConventions(devflowDir, current.provider, requested);
|
|
256
|
-
if (transition.kind === 'renamed') {
|
|
257
|
-
messages.push({
|
|
258
|
-
level: 'info',
|
|
259
|
-
text: `Moved the previous ${transition.previous} conventions aside: ${transition.to}`,
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
else if (transition.kind === 'failed') {
|
|
263
|
-
messages.push({ level: 'warn', text: transition.error });
|
|
264
|
-
}
|
|
265
|
-
// 4. Persist. Everything after this point advertises the persisted value.
|
|
266
167
|
await io.syncManifest(devflowDir, { provider: requested });
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
const artifacts = await io.convergeArtifacts(claudeDir, requested, (msg) => agentWarnings.push(msg));
|
|
270
|
-
for (const text of agentWarnings)
|
|
271
|
-
messages.push({ level: 'warn', text });
|
|
272
|
-
// 6 + 7. [DR-22] a selection change re-arms the attempt counter; [DR-10] the
|
|
273
|
-
// sentinel converges in both directions. A removal is always attempted, because
|
|
274
|
-
// a stale sentinel costs every future session a fork for a provider the user
|
|
275
|
-
// has left; the WRITE is gated on the agent being SPAWNABLE (design review C2).
|
|
276
|
-
//
|
|
277
|
-
// The gate is `agentPresent`, not `converged`. Reading `converged` alone is
|
|
278
|
-
// wrong in both directions: a re-copy that fails over an already-installed
|
|
279
|
-
// agent would disable a provider that still works, and merely SUPPRESSING the
|
|
280
|
-
// write leaves the previous provider's sentinel in place — so a jira → linear
|
|
281
|
-
// switch whose agent copy failed keeps advertising jira, which is the very
|
|
282
|
-
// state the suppression exists to prevent. Not-spawnable therefore REMOVES,
|
|
283
|
-
// through the one sentinel owner in src/core/tracker.ts (D-TRACKER-OWNER) —
|
|
284
|
-
// never an inline fs.rm here.
|
|
168
|
+
// [DR-22] a selection change re-arms the attempt counters; [DR-10] the
|
|
169
|
+
// sentinel converges in both directions.
|
|
285
170
|
const rearm = await io.rearmInference(devflowDir);
|
|
286
171
|
if (!rearm.ok)
|
|
287
172
|
messages.push({ level: 'warn', text: rearm.error });
|
|
288
|
-
const
|
|
289
|
-
const sentinel = await io.applySentinel(devflowDir, advertisable ? requested : DEFAULT_TRACKER_PROVIDER);
|
|
173
|
+
const sentinel = await io.applySentinel(devflowDir, requested);
|
|
290
174
|
if (!sentinel.ok)
|
|
291
175
|
messages.push({ level: 'warn', text: sentinel.error });
|
|
292
|
-
|
|
293
|
-
messages.push({
|
|
294
|
-
level: 'warn',
|
|
295
|
-
text: `Tracker sentinel removed — no ${requested} agent is installed, so nothing advertises a ` +
|
|
296
|
-
`provider whose agent is missing and no session will try to spawn it. ` +
|
|
297
|
-
`Re-run devflow tracker --set ${requested}.`,
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
const removed = overlay.pruned.removed.length;
|
|
301
|
-
const agentNote = artifacts.agent === 'unchanged' ? '' : `, tracker agent ${artifacts.agent}`;
|
|
302
|
-
const moved = overlay.overlaidRefs.length > 0 || removed > 0 || agentNote !== '';
|
|
176
|
+
const unchanged = requested === current.provider;
|
|
303
177
|
messages.push({
|
|
304
|
-
level:
|
|
305
|
-
text:
|
|
306
|
-
? `Tracker: ${requested} — ${overlay.overlaidRefs.length} installed, ${removed} removed${agentNote}`
|
|
307
|
-
: `Tracker: ${requested} (unchanged)`,
|
|
178
|
+
level: unchanged ? 'info' : 'success',
|
|
179
|
+
text: unchanged ? `Tracker: ${requested} (unchanged)` : `Tracker: ${requested}`,
|
|
308
180
|
});
|
|
309
|
-
return {
|
|
181
|
+
return { provider: requested, messages };
|
|
310
182
|
}
|
|
311
183
|
export const trackerCommand = new Command('tracker')
|
|
312
|
-
.description('Show or set the issue tracker provider')
|
|
313
|
-
.option('--status', 'Show the
|
|
314
|
-
.option('--set <id>', 'Set the issue tracker provider: github, jira, or linear')
|
|
184
|
+
.description('Show or set the machine\'s issue tracker provider')
|
|
185
|
+
.option('--status', 'Show the provider in effect and its learned conventions file, and re-arm background inference')
|
|
186
|
+
.option('--set <id>', 'Set the machine\'s default issue tracker provider: github, jira, or linear')
|
|
315
187
|
.action(async (options) => {
|
|
316
188
|
const devflowDir = getDevFlowDirectory();
|
|
317
189
|
const hasFlag = options.status || options.set !== undefined;
|
|
@@ -319,9 +191,9 @@ export const trackerCommand = new Command('tracker')
|
|
|
319
191
|
p.intro(color.bgCyan(color.white(' Tracker ')));
|
|
320
192
|
const validIds = TRACKER_PROVIDERS.map(t => `${t.id} — ${t.hint}`).join('\n ');
|
|
321
193
|
p.note(`${color.cyan('devflow tracker --status')} Show the provider and learned conventions\n` +
|
|
322
|
-
`${color.cyan('devflow tracker --set <id>')}
|
|
194
|
+
`${color.cyan('devflow tracker --set <id>')} Set the machine's default provider\n\n` +
|
|
323
195
|
`Valid provider IDs:\n ${validIds}`, 'Usage');
|
|
324
|
-
p.outro(color.dim('github is the default — devflow tracker --set github
|
|
196
|
+
p.outro(color.dim('github is the default — devflow tracker --set github returns to it'));
|
|
325
197
|
return;
|
|
326
198
|
}
|
|
327
199
|
// Validate --set input before any I/O (parse-don't-validate at the boundary).
|
|
@@ -343,9 +215,12 @@ export const trackerCommand = new Command('tracker')
|
|
|
343
215
|
// ── Status ─────────────────────────────────────────────────────────────────
|
|
344
216
|
// --status wins when both flags are passed, mirroring `devflow compliance`.
|
|
345
217
|
if (options.status) {
|
|
346
|
-
const
|
|
347
|
-
const
|
|
348
|
-
|
|
218
|
+
const settingsModule = loadSettingsModule();
|
|
219
|
+
const selection = repoTrackerSelection(settingsModule, { dir: process.cwd() });
|
|
220
|
+
const effective = selection?.provider ?? current.provider;
|
|
221
|
+
const conventions = await readTrackerStatusConventions(devflowDir, effective);
|
|
222
|
+
const mechanics = await readTrackerMechanics(getClaudeDirectory());
|
|
223
|
+
// [D-F] Inspecting the status re-arms the attempt counters. --status is
|
|
349
224
|
// the command a capped user reaches for to find out why nothing is being
|
|
350
225
|
// learned, so it is the command that has to hand back another five
|
|
351
226
|
// tries; the alternative leaves the only escape a hand deletion of an
|
|
@@ -358,24 +233,23 @@ export const trackerCommand = new Command('tracker')
|
|
|
358
233
|
const inference = statusRearm.ok
|
|
359
234
|
? `re-armed (${TRACKER_ATTEMPTS_MAX} attempts available)`
|
|
360
235
|
: 're-arm failed — see the warning below';
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
`Mechanics: ${formatTrackerMechanics(mechanics)}`,
|
|
369
|
-
`Inference: ${inference}`,
|
|
370
|
-
].join('\n'), 'Tracker Status');
|
|
236
|
+
p.note(formatTrackerStatus({
|
|
237
|
+
machine: current.provider,
|
|
238
|
+
selection,
|
|
239
|
+
conventions,
|
|
240
|
+
mechanics,
|
|
241
|
+
inference,
|
|
242
|
+
}), 'Tracker Status');
|
|
371
243
|
if (!statusRearm.ok)
|
|
372
244
|
p.log.warn(statusRearm.error);
|
|
245
|
+
const trackedWarning = personalConfigTrackedWarning(settingsModule, { dir: process.cwd() });
|
|
246
|
+
if (trackedWarning !== null)
|
|
247
|
+
p.log.warn(trackedWarning);
|
|
373
248
|
return;
|
|
374
249
|
}
|
|
375
250
|
// ── Set ────────────────────────────────────────────────────────────────────
|
|
376
251
|
const outcome = await runTrackerSet({
|
|
377
252
|
devflowDir,
|
|
378
|
-
claudeDir: getClaudeDirectory(),
|
|
379
253
|
current,
|
|
380
254
|
requested: setProvider ?? current.provider,
|
|
381
255
|
io: buildTrackerSetIO(),
|
|
@@ -396,8 +270,6 @@ export const trackerCommand = new Command('tracker')
|
|
|
396
270
|
break;
|
|
397
271
|
}
|
|
398
272
|
}
|
|
399
|
-
if (outcome.exitCode !== 0)
|
|
400
|
-
process.exit(outcome.exitCode);
|
|
401
273
|
if (outcome.provider !== DEFAULT_TRACKER_PROVIDER) {
|
|
402
274
|
p.log.info(color.dim('Your issue conventions are learned in the background at the next session start'));
|
|
403
275
|
}
|