devflow-kit 3.9.0 → 3.10.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 +111 -0
- package/README.md +1 -1
- package/dist/agents/code.md +6 -6
- package/dist/agents/fix.md +6 -6
- package/dist/agents/harden.md +7 -6
- package/dist/agents/refactor.md +6 -6
- package/dist/cli/commands/ambient.js +10 -2
- package/dist/cli/commands/compliance-prompts.js +9 -4
- package/dist/cli/commands/compliance.js +171 -24
- package/dist/cli/commands/curation.js +12 -7
- package/dist/cli/commands/gate-level-prompts.js +39 -25
- package/dist/cli/commands/gates.js +5 -7
- package/dist/cli/commands/init.js +142 -138
- package/dist/cli/commands/install-report.js +34 -0
- package/dist/cli/commands/legacy-sweep.js +64 -0
- package/dist/cli/commands/proxy.js +6 -1
- package/dist/cli/commands/rules.js +29 -12
- package/dist/cli/commands/skills.js +42 -9
- package/dist/cli/commands/tracker.js +9 -3
- package/dist/cli/commands/uninstall.js +277 -51
- package/dist/commands/bug-analysis.md +69 -120
- package/dist/commands/dynamic-build.md +23 -23
- package/dist/commands/implement.md +46 -32
- package/dist/commands/release.md +1 -1
- package/dist/commands/resolve.md +35 -31
- package/dist/commands/self-review.md +8 -7
- package/dist/compliance-off/agents/git.md +120 -0
- package/dist/compliance-off/agents/triage.md +180 -0
- package/dist/compliance-off/commands/bug-analysis.md +398 -0
- package/dist/compliance-off/commands/code-review.md +520 -0
- package/dist/compliance-off/commands/dynamic-build.md +1251 -0
- package/dist/compliance-off/commands/dynamic-tickets.md +676 -0
- package/dist/compliance-off/commands/implement.md +736 -0
- package/dist/compliance-off/commands/plan.md +671 -0
- package/dist/compliance-off/commands/release.md +309 -0
- package/dist/compliance-off/commands/resolve.md +742 -0
- package/dist/compliance-off/skills/git/references/decision-markers.md +17 -0
- package/dist/compliance-off/skills/git/references/learn-conventions.md +80 -0
- package/dist/compliance-off/skills/git/references/pr/check-ci-status.md +33 -0
- package/dist/compliance-off/skills/git/references/pr/check-merge-readiness.md +49 -0
- package/dist/compliance-off/skills/git/references/pr/fetch-review-threads.md +52 -0
- package/dist/compliance-off/skills/git/references/pr/validate-branch.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/_contract.md +33 -0
- package/dist/compliance-off/skills/git/references/tracker/_mcp.md +153 -0
- package/dist/compliance-off/skills/git/references/tracker/github/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/github/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/github/ensure-traceable-issue.md +74 -0
- package/dist/compliance-off/skills/git/references/tracker/github/fetch-issue.md +69 -0
- package/dist/compliance-off/skills/git/references/tracker/github/fetch-issues-batch.md +65 -0
- package/dist/compliance-off/skills/git/references/tracker/github/gather-release-evidence.md +48 -0
- package/dist/compliance-off/skills/git/references/tracker/github/setup-task.md +77 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/ensure-traceable-issue.md +61 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issue.md +51 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/fetch-issues-batch.md +63 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/gather-release-evidence.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/jira/setup-task.md +82 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/create-release.md +35 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/ensure-pr-ready.md +100 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/ensure-traceable-issue.md +61 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issue.md +51 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/fetch-issues-batch.md +63 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/gather-release-evidence.md +47 -0
- package/dist/compliance-off/skills/git/references/tracker/linear/setup-task.md +83 -0
- package/dist/compliance-off/skills/git/references/trust-rule.md +7 -0
- package/dist/core/agent-models.js +19 -4
- package/dist/core/assets.js +126 -17
- package/dist/core/compliance-off-files.js +27 -0
- package/dist/core/curation-variants.js +460 -112
- package/dist/core/flags.js +2 -2
- package/dist/core/installed-variants.js +63 -0
- package/dist/core/manifest.js +48 -5
- package/dist/core/mds-variants.js +700 -3
- package/dist/core/orphan-sweep.js +11 -2
- package/dist/core/plugins.js +65 -8
- package/dist/curation-compliance-off/agents/triage.md +176 -0
- package/dist/curation-compliance-off/commands/bug-analysis.md +349 -0
- package/dist/curation-compliance-off/commands/code-review.md +481 -0
- package/dist/curation-compliance-off/commands/dynamic-build.md +1211 -0
- package/dist/curation-compliance-off/commands/dynamic-tickets.md +634 -0
- package/dist/curation-compliance-off/commands/implement.md +689 -0
- package/dist/curation-compliance-off/commands/plan.md +631 -0
- package/dist/curation-compliance-off/commands/release.md +279 -0
- package/dist/curation-compliance-off/commands/resolve.md +692 -0
- package/dist/curation-off/agents/code.md +6 -6
- package/dist/curation-off/agents/fix.md +6 -6
- package/dist/curation-off/agents/harden.md +7 -6
- package/dist/curation-off/agents/refactor.md +6 -6
- package/dist/curation-off/commands/bug-analysis.md +69 -119
- package/dist/curation-off/commands/dynamic-build.md +23 -23
- package/dist/curation-off/commands/implement.md +46 -32
- package/dist/curation-off/commands/release.md +1 -1
- package/dist/curation-off/commands/resolve.md +35 -31
- package/dist/curation-off/commands/self-review.md +8 -7
- package/dist/skills/git/references/pr/resolve-review-threads.md +1 -1
- package/dist/targets/claude-code/behavior-install.js +23 -9
- package/dist/targets/claude-code/charter-render.js +374 -0
- package/dist/targets/claude-code/compliance-install.js +32 -12
- package/dist/targets/claude-code/compliance-off-files.js +245 -0
- package/dist/targets/claude-code/curation-install.js +570 -100
- package/dist/targets/claude-code/installer.js +467 -62
- package/dist/targets/claude-code/language-stamp.js +6 -0
- package/dist/targets/claude-code/linked-folder.js +182 -0
- package/dist/targets/claude-code/reference-converge.js +118 -0
- package/dist/targets/claude-code/tracker-install.js +21 -8
- package/dist/targets/claude-code/tracker-servers.js +20 -6
- package/dist/targets/claude-code/workflow-folder-install.js +231 -0
- package/dist/variant-axes.json +109 -0
- package/package.json +1 -1
- package/src/assets/agents/_partials/_code.mds +6 -6
- package/src/assets/agents/evaluate.md +1 -1
- package/src/assets/agents/git.mds +32 -0
- package/src/assets/agents/harden.mds +7 -6
- package/src/assets/agents/test.md +21 -12
- package/src/assets/agents/triage.mds +2 -0
- package/src/assets/agents/validate.md +10 -10
- package/src/assets/commands/_partials/_compliance.mds +5 -3
- package/src/assets/commands/_partials/_engine.mds +9 -8
- package/src/assets/commands/_partials/_publication.mds +10 -1
- package/src/assets/commands/bug-analysis.mds +80 -126
- package/src/assets/commands/code-review.mds +43 -0
- package/src/assets/commands/dynamic-build.mds +109 -15
- package/src/assets/commands/dynamic-tickets.mds +13 -0
- package/src/assets/commands/implement.mds +145 -33
- package/src/assets/commands/plan.mds +19 -0
- package/src/assets/commands/release.mds +50 -1
- package/src/assets/commands/resolve.mds +217 -32
- package/src/assets/commands/self-review.mds +8 -7
- package/src/assets/mds/git/_ops.mds +23 -0
- package/src/assets/mds/git/_ops_steps.mds +17 -0
- package/src/assets/mds/git/_pr.mds +1 -1
- package/src/assets/mds/git/_references.mds +6 -0
- package/src/assets/mds/tracker/_common.mds +4 -0
- package/src/assets/mds/tracker/_github.mds +42 -0
- package/src/assets/mds/tracker/_jira.mds +43 -0
- package/src/assets/mds/tracker/_linear.mds +43 -0
- package/src/assets/rules/quality.md +2 -0
- package/src/assets/scripts/ci-wait.cjs +204 -126
- package/src/assets/scripts/hooks/assets/implement-installed +1 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +13 -11
- package/src/assets/scripts/hooks/preamble +18 -8
- package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
- package/src/assets/scripts/pr-evidence.cjs +41 -2
- package/src/assets/scripts/verify-evidence.cjs +408 -56
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +3 -3
- package/src/assets/skills/agent-references/references/commands/plan-levels.md +1 -1
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +6 -2
- package/src/assets/skills/agent-references/references/validate/passed-alone.md +7 -7
- package/src/assets/skills/docs-framework/SKILL.md +5 -2
- package/src/assets/skills/git/SKILL.md +3 -3
- package/src/assets/skills/git/references/github-api.md +2 -139
- package/src/assets/skills/git/references/github-posting.md +158 -0
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/references/patterns.md +1 -1
- package/src/assets/skills/review-methodology/references/report-template.md +1 -2
- package/src/assets/skills/testing/SKILL.md +2 -4
|
@@ -9,7 +9,7 @@ import { loadShippedAgentDefaults } from '../../core/agent-models.js';
|
|
|
9
9
|
import { readMachineFeature, writeMachineFeature } from '../../core/feature-switch.js';
|
|
10
10
|
import { loadSettingsModule, narrowedSwitchLabel, personalConfigTrackedWarning } from '../../core/evidence-policy.js';
|
|
11
11
|
import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
|
|
12
|
-
import {
|
|
12
|
+
import { applyVariantToggle, describeCurationConverge, describePostingLeft, installedScriptsSkew, readRunningVersion, } from '../../targets/claude-code/curation-install.js';
|
|
13
13
|
import { getLedgerRoot } from '../../core/ledger-root.js';
|
|
14
14
|
import { AREA_PAUSE_NOTICE } from '../../core/area-pause-notice.js';
|
|
15
15
|
import { probeTrackedArea } from '../../core/tracked-areas.js';
|
|
@@ -643,13 +643,15 @@ async function handleClear() {
|
|
|
643
643
|
/**
|
|
644
644
|
* Make the installed prompts match the switch just written
|
|
645
645
|
* (D-LEARNING-VARIANT-INSTALL): the curation-on or curation-off commands and
|
|
646
|
-
* agents, and the apply-decisions skill. The
|
|
647
|
-
*
|
|
648
|
-
* overrides survive.
|
|
646
|
+
* agents, and the apply-decisions skill. The one converge for both axes that
|
|
647
|
+
* `devflow init` and `devflow compliance` run, followed by reapplying the saved
|
|
648
|
+
* agent model mapping so `devflow agents` overrides survive.
|
|
649
649
|
*
|
|
650
650
|
* Warn-not-fail: the switch itself is already recorded, and either mismatch is
|
|
651
651
|
* safe (an on variant is still gated at run time, an off variant loads no
|
|
652
|
-
* decisions), so a problem here never changes the exit code.
|
|
652
|
+
* decisions), so a problem here never changes the exit code. A compliance-off
|
|
653
|
+
* machine that still has a prompt that posts is a warning here too: the fail-closed
|
|
654
|
+
* exit belongs to `devflow compliance`, which changes that axis.
|
|
653
655
|
*
|
|
654
656
|
* Answers whether the converge ran, which means the installed files are the ones
|
|
655
657
|
* this CLI writes. The toggle moves the ledger folder only then: under version skew
|
|
@@ -657,10 +659,11 @@ async function handleClear() {
|
|
|
657
659
|
*/
|
|
658
660
|
async function convergeInstalledVariants(enabled) {
|
|
659
661
|
try {
|
|
660
|
-
const outcome = await
|
|
662
|
+
const outcome = await applyVariantToggle({
|
|
661
663
|
claudeDir: getClaudeDirectory(),
|
|
662
664
|
devflowDir: getDevFlowDirectory(),
|
|
663
|
-
|
|
665
|
+
axis: 'curation',
|
|
666
|
+
enabled,
|
|
664
667
|
runningVersion: await readRunningVersion(),
|
|
665
668
|
warn: (msg) => p.log.warn(msg),
|
|
666
669
|
});
|
|
@@ -671,6 +674,8 @@ async function convergeInstalledVariants(enabled) {
|
|
|
671
674
|
const changed = describeCurationConverge(outcome.result, enabled);
|
|
672
675
|
if (changed !== null)
|
|
673
676
|
p.log.info(color.dim(`Installed prompts: ${changed}`));
|
|
677
|
+
for (const line of describePostingLeft(outcome.result.posting))
|
|
678
|
+
p.log.warn(line);
|
|
674
679
|
return 'converged';
|
|
675
680
|
}
|
|
676
681
|
catch (err) {
|
|
@@ -31,23 +31,18 @@ import { DEFAULT_GATE_LEVEL, GATE_LEVELS, MALFORMED_GATE_LEVEL_READS_AS, parseGa
|
|
|
31
31
|
/** Message shown on the gate level select prompt. */
|
|
32
32
|
export const GATE_LEVEL_SELECT_MESSAGE = 'Gate level for this machine';
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* level
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* focuses and the levels that skip Polish). The clauses that lean skips Harden on a
|
|
42
|
-
* docs-only diff, that standard and lean run one Evaluate pass and that standard and lean
|
|
43
|
-
* apply severity tiers in /resolve have no facet row in `tests/gate-level-prompts.test.ts`:
|
|
44
|
-
* the docs guard checks each at every doc site, against its row in the /implement or
|
|
45
|
-
* /resolve level reference.
|
|
34
|
+
* One short line for each level, as the select hint and the catalogue show it.
|
|
35
|
+
* clack never wraps a note or a hint, so a hint stays within 60 characters and holds no line
|
|
36
|
+
* break (a guard in `tests/gate-level-prompts.test.ts` holds both). The full effects of each
|
|
37
|
+
* level live in GATE_LEVEL_EFFECT in gates.ts, in `devflow gates` and in the docs.
|
|
38
|
+
* Change a hint when the level it names changes how much it checks; then check
|
|
39
|
+
* GATE_LEVEL_EFFECT and update the pin. The comment on GATE_LEVEL_EFFECT holds the full
|
|
40
|
+
* update rule, the pin and the list of clauses that the two tables share.
|
|
46
41
|
*/
|
|
47
42
|
export const GATE_LEVEL_HINTS = Object.freeze({
|
|
48
|
-
full: 'the most checking: every review focus,
|
|
49
|
-
standard: 'fewer review focuses
|
|
50
|
-
lean: 'the least checking
|
|
43
|
+
full: 'the most checking: every review focus, and Polish',
|
|
44
|
+
standard: 'fewer review focuses, no Polish; the default',
|
|
45
|
+
lean: 'the least checking: no Polish, Validate runs the lint pack',
|
|
51
46
|
});
|
|
52
47
|
/** Build clack select options for the gate level list, most to least checking. */
|
|
53
48
|
export function gateLevelChoices() {
|
|
@@ -55,12 +50,24 @@ export function gateLevelChoices() {
|
|
|
55
50
|
}
|
|
56
51
|
/**
|
|
57
52
|
* Format a padded level catalogue suitable for a clack note body.
|
|
58
|
-
* Produces: ` standard fewer review focuses
|
|
53
|
+
* Produces: ` standard fewer review focuses, no Polish; the default`
|
|
59
54
|
*/
|
|
60
55
|
export function formatGateLevelCatalogue() {
|
|
61
56
|
const width = Math.max(...GATE_LEVELS.map(level => level.length));
|
|
62
57
|
return 'Levels:\n' + GATE_LEVELS.map(level => ` ${level.padEnd(width)} ${GATE_LEVEL_HINTS[level]}`).join('\n');
|
|
63
58
|
}
|
|
59
|
+
// ── Where the stored level was found ───────────────────────────────────────────
|
|
60
|
+
/**
|
|
61
|
+
* Where init found the stored machine gate level:
|
|
62
|
+
*
|
|
63
|
+
* manifest in the manifest init reads (or nowhere, on a first install or after
|
|
64
|
+
* --reset): the seed holds it
|
|
65
|
+
* rejected in a manifest that readManifest() refuses, read raw: init keeps the
|
|
66
|
+
* level as stored while it replaces that manifest
|
|
67
|
+
* unreadable nowhere: the manifest cannot be read at all, or stores a level that cannot be
|
|
68
|
+
* written back as JSON, so what it stored is unknown
|
|
69
|
+
*/
|
|
70
|
+
export const STORED_GATE_LEVEL_ORIGINS = Object.freeze(['manifest', 'rejected', 'unreadable']);
|
|
64
71
|
/** `features.gateLevel` of a raw manifest, exactly as stored; `undefined` for an absent key. */
|
|
65
72
|
function rawGateLevelOf(raw) {
|
|
66
73
|
const features = raw.features;
|
|
@@ -228,17 +235,24 @@ export function describeGateLevel(stored) {
|
|
|
228
235
|
export function gateLevelPromptSeed(stored, origin = 'manifest') {
|
|
229
236
|
return appliedLevel(classifyGateLevel(stored, origin));
|
|
230
237
|
}
|
|
231
|
-
/** The "Current setting" lines of the prompt note, from the stored value and where it was found. */
|
|
238
|
+
/** The "Current setting" lines of the prompt note, from the stored value and where it was found. Each line fits the 72 columns the note allows. */
|
|
232
239
|
function currentSettingLines(stored, origin) {
|
|
233
240
|
switch (origin) {
|
|
234
241
|
case 'manifest':
|
|
235
242
|
return `Current setting: ${describeGateLevel(stored)}`;
|
|
236
243
|
case 'rejected':
|
|
237
|
-
return
|
|
238
|
-
|
|
244
|
+
return [
|
|
245
|
+
`Current setting: ${describeGateLevel(stored)}`,
|
|
246
|
+
'It comes from a manifest that devflow rejects.',
|
|
247
|
+
'Init replaces that manifest and keeps this level',
|
|
248
|
+
'unless you choose another.',
|
|
249
|
+
].join('\n');
|
|
239
250
|
case 'unreadable':
|
|
240
|
-
return
|
|
241
|
-
|
|
251
|
+
return [
|
|
252
|
+
'Current setting: unknown',
|
|
253
|
+
`The stored gate level cannot be read, so ${DEFAULT_GATE_LEVEL} applies`,
|
|
254
|
+
'until you choose a level.',
|
|
255
|
+
].join('\n');
|
|
242
256
|
default: {
|
|
243
257
|
const exhaustive = origin;
|
|
244
258
|
return exhaustive;
|
|
@@ -320,10 +334,10 @@ export function buildClackGateLevelPrompts() {
|
|
|
320
334
|
export async function runGateLevelStep(opts) {
|
|
321
335
|
const { stored, storedOrigin = 'manifest', prompts } = opts;
|
|
322
336
|
prompts.note(`${currentSettingLines(stored, storedOrigin)}\n\n` +
|
|
323
|
-
'Sets how much checking /code-review, /plan
|
|
324
|
-
'machine. A personal "gateLevel" in a repository\'s
|
|
325
|
-
'overrides it in that repository. Change it later
|
|
326
|
-
'devflow gates --set <level>.\n\n' +
|
|
337
|
+
'Sets how much checking /code-review, /plan, /implement and /resolve\n' +
|
|
338
|
+
'apply on this machine. A personal "gateLevel" in a repository\'s\n' +
|
|
339
|
+
'.devflow/config.json overrides it in that repository. Change it later\n' +
|
|
340
|
+
'with devflow gates --set <level>.\n\n' +
|
|
327
341
|
formatGateLevelCatalogue(), 'Gate level');
|
|
328
342
|
const outcome = await prompts.selectLevel({
|
|
329
343
|
message: GATE_LEVEL_SELECT_MESSAGE,
|
|
@@ -39,16 +39,14 @@ export const GATE_LEVEL_MEANINGS = Object.freeze({
|
|
|
39
39
|
/**
|
|
40
40
|
* Which commands read the level today; each later command that reads it updates this text.
|
|
41
41
|
*
|
|
42
|
-
* The init prompt
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* The init prompt gives each level one short line in GATE_LEVEL_HINTS (gate-level-prompts.ts);
|
|
43
|
+
* this text and the docs hold the full effects. Change the hints too when a level changes
|
|
44
|
+
* how much it checks. A command that reads the level and has no rule that changes a phase
|
|
45
|
+
* leaves the hints true, so it changes this text alone.
|
|
45
46
|
* One test pins the exact text of both tables, so a change to either fails it until you
|
|
46
47
|
* check the other table and update the pin; further rows check the clauses the two tables
|
|
47
48
|
* share (the lint pack level, the levels that run fewer review focuses and the levels that
|
|
48
|
-
* skip Polish).
|
|
49
|
-
* and lean run one Evaluate pass and that standard and lean apply severity tiers in /resolve
|
|
50
|
-
* have no facet row in `tests/gate-level-prompts.test.ts`: the docs guard checks each at
|
|
51
|
-
* every doc site, against its row in the /implement or /resolve level reference.
|
|
49
|
+
* skip Polish).
|
|
52
50
|
*/
|
|
53
51
|
export const GATE_LEVEL_EFFECT = '/code-review and /plan read the level: standard and lean run fewer review focuses and fewer planning agents. At lean, /implement has Validate run the project\'s lint pack. /implement and /resolve also read the level and name it in their report; at standard and lean they skip Polish, and full keeps it. At lean, /implement skips Harden on a docs-only diff. At standard and lean, /implement runs one Evaluate pass and at most one Fix round. At standard and lean, /resolve applies severity tiers: it fixes or escalates every security finding, fixes CRITICAL, HIGH and severe MEDIUM findings in the branch\'s own code, fixes prose-only MEDIUM findings with text edits alone, and sends lower findings and pre-existing non-security findings to one backlog issue.';
|
|
54
52
|
const MACHINE_MANIFEST_NAME = 'manifest.json';
|
|
@@ -14,11 +14,13 @@ import { loadCurationMove } from '../../core/curation-move.js';
|
|
|
14
14
|
import { describeMoveText, describeOutcome, runInitCurationMove, shownSkipAt } from './curation-move-step.js';
|
|
15
15
|
import { realGlobalTuningMoveIO, realProjectLedgerMoveIO } from './curation-move-io.js';
|
|
16
16
|
import { describeEchoedValue } from '../../core/echo-value.js';
|
|
17
|
-
import { installViaFileCopy,
|
|
18
|
-
import { formatOverlaySummary, formatSkillScopeSummary, formatTrackerAssetSummary, isPluginListUnchanged } from './install-report.js';
|
|
17
|
+
import { installViaFileCopy, installScripts } from '../../targets/claude-code/installer.js';
|
|
18
|
+
import { formatLinksSummary, formatOverlaySummary, formatSkillScopeSummary, formatTrackerAssetSummary, isPluginListUnchanged } from './install-report.js';
|
|
19
|
+
import { mergeLinkReports, newLinkReport } from '../../targets/claude-code/linked-folder.js';
|
|
20
|
+
import { sweepLegacyDevflowFiles } from './legacy-sweep.js';
|
|
19
21
|
import { convergeTrackerArtifacts } from '../../targets/claude-code/tracker-install.js';
|
|
20
22
|
import { installSettings, installManagedSettings, installClaudeignore, hasClaudeignore, discoverProjectGitRoots, ensureDevflowGitignore, applyUserSecurityDenyList, detectDenyState, resolveSecurityAction, assertHistoricalDenySuperset, loadTemplateDenyEntries, stripUserSecurityDenyList, } from '../../targets/claude-code/post-install.js';
|
|
21
|
-
import { DEVFLOW_PLUGINS,
|
|
23
|
+
import { DEVFLOW_PLUGINS, registeredPluginNames, buildAssetMaps, buildScopedSkillsMap, buildRulesMap, partitionSelectablePlugins, WORKFLOW_ORDER, parsePluginSelection, resolveFeatureRedirect } from '../../core/plugins.js';
|
|
22
24
|
import { LEGACY_SKILL_NAMES } from '../../targets/claude-code/legacy.js';
|
|
23
25
|
import { detectPlatform, detectShell, getProfilePath, getSafeDeleteInfo, hasSafeDelete } from '../../core/safe-delete.js';
|
|
24
26
|
import { generateSafeDeleteBlock, installToProfile, removeFromProfile, getInstalledVersion, SAFE_DELETE_BLOCK_VERSION } from '../../core/safe-delete-install.js';
|
|
@@ -49,8 +51,8 @@ import { applyTrackerSentinel, effectiveTrackerServers, parseTrackerId, rearmTra
|
|
|
49
51
|
import { formatTrackerSummary, shouldRunTrackerStep, runTrackerStep, buildClackTrackerPrompts, } from './tracker-prompts.js';
|
|
50
52
|
import { buildClackGateLevelPrompts, carriedGateLevel, formatGateLevelRow, formatGateLevelSummary, readStoredGateLevel, resolveGateLevelForInit, resolveGateLevelInitState, } from './gate-level-prompts.js';
|
|
51
53
|
import { shouldRunAttributionStep, runAttributionStep, buildClackAttributionPrompts, applyAttributionAnswer, attributionSeedFrom, } from './attribution-prompts.js';
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
+
import { convergeVariantAxes, describePostingLeft, installedVersionMatches, readRunningVersion } from '../../targets/claude-code/curation-install.js';
|
|
55
|
+
import { POSTING_OFF_NOTICE, installRecordFor, postingNoticeDue, readInstalledVariants } from '../../core/installed-variants.js';
|
|
54
56
|
import { auditAndRecordClaudeMd, formatClaudeMdAuditNote, formatClaudeMdAuditUnavailable, } from '../../core/claude-md-audit.js';
|
|
55
57
|
import * as os from 'os';
|
|
56
58
|
// Re-export pure functions for tests (canonical source is post-install.ts)
|
|
@@ -145,6 +147,43 @@ function logSummaryLines(lines) {
|
|
|
145
147
|
}
|
|
146
148
|
}
|
|
147
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Converge the installed agents onto the saved model and effort assignments, after the file copy
|
|
152
|
+
* wrote the shipped defaults. Per-item failures are non-fatal.
|
|
153
|
+
*
|
|
154
|
+
* Init-only optimization: with an empty mapping and the proxy off, every agent already holds its
|
|
155
|
+
* shipped default, so the walk would read the agent files and write none; it is skipped. The
|
|
156
|
+
* disable and revert paths call `reapplyAgentMapping` directly and always need the full walk.
|
|
157
|
+
* `hasAgentMappingEntries` counts agent entries only: an `agents.memory`-only mapping names no
|
|
158
|
+
* installed file, so it keeps this gate closed (D-WORKER-AGENTS).
|
|
159
|
+
*
|
|
160
|
+
* The links this walk met are returned, not printed: the caller prints them once, through the same
|
|
161
|
+
* summary as the install step. `verbose` prints the other warnings of the walk.
|
|
162
|
+
*/
|
|
163
|
+
export async function reapplyAgentMappingForInit(opts) {
|
|
164
|
+
const { proxyEnabled, claudeDir, devflowDir, verbose } = opts;
|
|
165
|
+
const preCheckMapping = await readAgentMapping(devflowDir);
|
|
166
|
+
const hasMappingEntries = preCheckMapping.ok && hasAgentMappingEntries(preCheckMapping.value);
|
|
167
|
+
if (!hasMappingEntries && !proxyEnabled)
|
|
168
|
+
return newLinkReport();
|
|
169
|
+
const result = await reapplyAgentMapping({
|
|
170
|
+
proxyEnabled,
|
|
171
|
+
installDir: path.join(claudeDir, 'agents', 'devflow'),
|
|
172
|
+
devflowDir,
|
|
173
|
+
});
|
|
174
|
+
const links = { replacedLinks: result.replacedLinks, refusedFolders: result.refusedFolders };
|
|
175
|
+
if (verbose) {
|
|
176
|
+
const linkLines = new Set(formatLinksSummary(links).map(line => line.message));
|
|
177
|
+
for (const warning of result.warnings) {
|
|
178
|
+
if (!linkLines.has(warning))
|
|
179
|
+
p.log.warn(warning);
|
|
180
|
+
}
|
|
181
|
+
if (result.updated.length > 0) {
|
|
182
|
+
p.log.info(`Agent model mapping reapplied: ${result.updated.length} agent(s) updated`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return links;
|
|
186
|
+
}
|
|
148
187
|
/**
|
|
149
188
|
* Classify the safe-delete installation state based on the installed version
|
|
150
189
|
* in the user's shell profile.
|
|
@@ -591,6 +630,20 @@ export function buildHudOnlyManifest(existing, version, now, refusedGateLevel) {
|
|
|
591
630
|
updatedAt: now,
|
|
592
631
|
};
|
|
593
632
|
}
|
|
633
|
+
/**
|
|
634
|
+
* The plugin names `init --hud-only` renders the orchestrator charter for. Pure.
|
|
635
|
+
*
|
|
636
|
+
* D-CHARTER-INSTALL-RENDER: --hud-only reinstalls no plugin, so the installed charter must keep describing
|
|
637
|
+
* what the manifest says is installed: the recorded plugins, with a deleted name pruned and a legacy name
|
|
638
|
+
* mapped as every other partial run does (`resolvePluginList`), and only names this registry has kept
|
|
639
|
+
* ({@link registeredPluginNames}). With no manifest, or one `readManifest()` refused, nothing is known to be installed, so the set is EMPTY: the
|
|
640
|
+
* render then names no workflow, and no sentinel is written. The registry fallback that uninstall takes
|
|
641
|
+
* would hand a machine with no record every workflow's routing line, which is the failure the render exists
|
|
642
|
+
* to prevent.
|
|
643
|
+
*/
|
|
644
|
+
export function hudOnlyCharterPlugins(manifest) {
|
|
645
|
+
return registeredPluginNames(resolvePluginList([], manifest, true));
|
|
646
|
+
}
|
|
594
647
|
/**
|
|
595
648
|
* The line `init --hud-only` prints when the version the manifest records is not the
|
|
596
649
|
* running version, or null when there is nothing to say. Pure.
|
|
@@ -704,8 +757,8 @@ export const initCommand = new Command('init')
|
|
|
704
757
|
.option('--no-proxy', 'Disable external model routing')
|
|
705
758
|
.option('--behavior', 'Enable the behavior rule (agents reply in ASD-STE100 Simplified Technical English; off by default)')
|
|
706
759
|
.option('--no-behavior', 'Disable the behavior rule')
|
|
707
|
-
.option('--compliance <list>', 'Enable compliance with comma-separated framework IDs (e.g., gdpr,hipaa)')
|
|
708
|
-
.option('--no-compliance', 'Disable compliance (removes the rule; the skill and framework references stay installed; frameworks remembered for re-enable)')
|
|
760
|
+
.option('--compliance <list>', 'Enable compliance with comma-separated framework IDs (e.g., gdpr,hipaa); installs the steps that post evidence and summaries to GitHub or a tracker')
|
|
761
|
+
.option('--no-compliance', 'Disable compliance (removes the rule and every step that posts evidence or summaries to GitHub or a tracker; the skill and framework references stay installed; frameworks remembered for re-enable)')
|
|
709
762
|
.option('--tracker <id>', 'The machine\'s default issue tracker provider: github, jira, or linear')
|
|
710
763
|
.option('--gate-level <full|standard|lean>', 'The machine\'s default gate level: full, standard, or lean (skips the gate level prompt)')
|
|
711
764
|
.option('--security <mode>', 'Security deny list location: user, managed, or none', /^(user|managed|none)$/i)
|
|
@@ -781,22 +834,28 @@ export const initCommand = new Command('init')
|
|
|
781
834
|
p.log.error(`Failed to update settings: ${error instanceof Error ? error.message : error}`);
|
|
782
835
|
process.exit(1);
|
|
783
836
|
}
|
|
784
|
-
// Install HUD scripts to devflow dir
|
|
785
|
-
const scriptsTarget = path.join(devflowDir, 'scripts');
|
|
786
|
-
try {
|
|
787
|
-
await composeScripts(scriptsTarget);
|
|
788
|
-
}
|
|
789
|
-
catch (error) {
|
|
790
|
-
p.log.error(`Failed to install HUD scripts: ${error instanceof Error ? error.message : error}`);
|
|
791
|
-
process.exit(1);
|
|
792
|
-
}
|
|
793
837
|
// Read the existing manifest: over a prior install, --hud-only touches only
|
|
794
|
-
// the HUD (D-HUD-ONLY-PRESERVE in buildHudOnlyManifest).
|
|
838
|
+
// the HUD (D-HUD-ONLY-PRESERVE in buildHudOnlyManifest). Read before the scripts are
|
|
839
|
+
// installed, so the charter render works from the record as the run found it.
|
|
795
840
|
let existingHudManifest = null;
|
|
796
841
|
try {
|
|
797
842
|
existingHudManifest = await readManifest(devflowDir);
|
|
798
843
|
}
|
|
799
844
|
catch { /* absent on fresh install — existingHudManifest stays null */ }
|
|
845
|
+
// Install HUD scripts to devflow dir. The orchestrator charter and the implement sentinel
|
|
846
|
+
// render for the plugins the manifest records, and for the empty set when it records none
|
|
847
|
+
// (D-CHARTER-INSTALL-RENDER). A render failure is a warning.
|
|
848
|
+
try {
|
|
849
|
+
await installScripts({
|
|
850
|
+
scriptsTarget: path.join(devflowDir, 'scripts'),
|
|
851
|
+
plugins: hudOnlyCharterPlugins(existingHudManifest),
|
|
852
|
+
warn: (msg) => p.log.warn(msg),
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
catch (error) {
|
|
856
|
+
p.log.error(`Failed to install HUD scripts: ${error instanceof Error ? error.message : error}`);
|
|
857
|
+
process.exit(1);
|
|
858
|
+
}
|
|
800
859
|
// A manifest readManifest() refuses reads as none, but its machine gate level is still
|
|
801
860
|
// stored there: --hud-only keeps it and says so, as the full init does. --reset does not
|
|
802
861
|
// apply here, so nothing asks to forget it.
|
|
@@ -1312,9 +1371,9 @@ export const initCommand = new Command('init')
|
|
|
1312
1371
|
}
|
|
1313
1372
|
else {
|
|
1314
1373
|
p.note('Puts every session (git repos only) in orchestrator posture:\n' +
|
|
1315
|
-
'a ~
|
|
1374
|
+
'a charter of up to ~850 tokens at session start plus a per-prompt reminder\n' +
|
|
1316
1375
|
'steer the main model to delegate work to agents and devflow workflows\n' +
|
|
1317
|
-
'instead of doing it mainline. Plan-mode handoffs
|
|
1376
|
+
'instead of doing it mainline. Plan-mode handoffs run devflow:implement when it is installed.', 'Ambient Mode');
|
|
1318
1377
|
const ambientChoice = await p.select({
|
|
1319
1378
|
message: 'Enable ambient mode?',
|
|
1320
1379
|
options: [
|
|
@@ -1810,6 +1869,10 @@ export const initCommand = new Command('init')
|
|
|
1810
1869
|
// their say (D-LEARNING-VARIANT-INSTALL): the files installed here and the
|
|
1811
1870
|
// manifest written at the end of init cannot disagree about it.
|
|
1812
1871
|
curation: curationEnabled,
|
|
1872
|
+
// The SETTLED compliance switch, never a raw seed or a repository's narrowing: the
|
|
1873
|
+
// variant follows the machine switch alone, and with it off the install refuses
|
|
1874
|
+
// before it writes a file when the variant files are not all there.
|
|
1875
|
+
compliance: complianceEnabled,
|
|
1813
1876
|
trackerServers,
|
|
1814
1877
|
// Non-fatal install notices with no other channel (skipped symlinks in the
|
|
1815
1878
|
// generated reference tree, mode-normalisation failures) reach the user rather
|
|
@@ -1822,55 +1885,44 @@ export const initCommand = new Command('init')
|
|
|
1822
1885
|
p.log.error(`${error}`);
|
|
1823
1886
|
process.exit(1);
|
|
1824
1887
|
}
|
|
1825
|
-
// D-LEARNING-VARIANT-INSTALL:
|
|
1826
|
-
// installed the variant of everything IT installed; on a `--plugin` install the
|
|
1827
|
-
// other plugins' files, already on disk, may still be
|
|
1828
|
-
//
|
|
1829
|
-
//
|
|
1830
|
-
//
|
|
1831
|
-
//
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
//
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
// (D-COMPLIANCE-INSTALL-ALWAYS) — so removedPreexisting reports the rule alone, on a
|
|
1864
|
-
// partial install; hadComplianceRule covers full installs, where installViaFileCopy
|
|
1865
|
-
// wipes the rules dir before converge probes it.
|
|
1866
|
-
if (!complianceEnabled && (convergeResult.removedPreexisting || hadComplianceRule)) {
|
|
1867
|
-
p.log.info('Compliance rule removed — if you previously had devflow-compliance installed, ' +
|
|
1868
|
-
'run `devflow compliance --enable` to re-enable with your framework selection.');
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
catch (err) {
|
|
1872
|
-
p.log.warn(`Compliance artifact convergence failed — install succeeded but compliance artifacts may be stale: ` +
|
|
1873
|
-
`${err instanceof Error ? err.message : String(err)}`);
|
|
1888
|
+
// The one converge for both switches (D-LEARNING-VARIANT-INSTALL, AC-27): the file copy
|
|
1889
|
+
// above installed the variant of everything IT installed; on a `--plugin` install the
|
|
1890
|
+
// other plugins' files, already on disk, may still be another variant (this run may
|
|
1891
|
+
// have flipped a switch), and the apply-decisions skill is removed here when the copy
|
|
1892
|
+
// skipped it on a partial install. It also converges the compliance skill and rule, so
|
|
1893
|
+
// enabling or disabling compliance during init needs no separate devflow compliance run.
|
|
1894
|
+
// Rewrites only what is installed. Warn-not-abort: a failure is a warning here and the
|
|
1895
|
+
// install record below is withheld, so the status line says to run init again. The agent
|
|
1896
|
+
// mapping is reapplied below.
|
|
1897
|
+
const variantConverge = await convergeVariantAxes({
|
|
1898
|
+
claudeDir,
|
|
1899
|
+
devflowDir,
|
|
1900
|
+
manifest: {
|
|
1901
|
+
features: {
|
|
1902
|
+
curation: curationEnabled,
|
|
1903
|
+
compliance: { enabled: complianceEnabled, frameworks: complianceFrameworks },
|
|
1904
|
+
rules: rulesEnabled,
|
|
1905
|
+
},
|
|
1906
|
+
},
|
|
1907
|
+
plugins: effectivePlugins,
|
|
1908
|
+
warn: (msg) => installWarnings.push(msg),
|
|
1909
|
+
});
|
|
1910
|
+
if (verbose && variantConverge.converged) {
|
|
1911
|
+
p.log.info(`Curation ${curationEnabled ? 'on' : 'off'}, compliance ${complianceEnabled ? 'on' : 'off'} variants: ` +
|
|
1912
|
+
`${variantConverge.commandsRewritten.length} command(s) and ${variantConverge.agentsRewritten.length} agent(s) ` +
|
|
1913
|
+
`rewritten, ${variantConverge.unchanged} already current`);
|
|
1914
|
+
}
|
|
1915
|
+
// A compliance-off converge that cannot prove the installed prompts free of posting steps
|
|
1916
|
+
// warns and names the files; init never exits non-zero for it (AC-28 binds the compliance command).
|
|
1917
|
+
installWarnings.push(...describePostingLeft(variantConverge.posting));
|
|
1918
|
+
// I41: emit legacy-upgrade notice when compliance is disabled AND a pre-existing rule
|
|
1919
|
+
// was found. The skill is no signal — converge installs it on every machine
|
|
1920
|
+
// (D-COMPLIANCE-INSTALL-ALWAYS) — so removedPreexisting reports the rule alone, on a
|
|
1921
|
+
// partial install; hadComplianceRule covers full installs, where installViaFileCopy
|
|
1922
|
+
// wipes the rules dir before converge probes it.
|
|
1923
|
+
if (!complianceEnabled && (variantConverge.compliance.removedPreexisting || hadComplianceRule)) {
|
|
1924
|
+
p.log.info('Compliance rule removed — if you previously had devflow-compliance installed, ' +
|
|
1925
|
+
'run `devflow compliance --enable` to re-enable with your framework selection.');
|
|
1874
1926
|
}
|
|
1875
1927
|
// D-BEHAVIOR-OPT-IN: converge the behavior rule. The plugin installer never copies it (no
|
|
1876
1928
|
// plugin declares it) and a full install wiped rules/devflow/ above, so this is the one
|
|
@@ -1899,52 +1951,12 @@ export const initCommand = new Command('init')
|
|
|
1899
1951
|
if (staleRemoved > 0 && verbose) {
|
|
1900
1952
|
p.log.info(`Cleaned up ${staleRemoved} legacy skill(s)`);
|
|
1901
1953
|
}
|
|
1902
|
-
// Clean up stale commands
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
for (const legacy of LEGACY_COMMAND_NAMES) {
|
|
1906
|
-
for (const suffix of ['.md']) {
|
|
1907
|
-
const legacyPath = path.join(commandsDir, `${legacy}${suffix}`);
|
|
1908
|
-
try {
|
|
1909
|
-
await fs.rm(legacyPath);
|
|
1910
|
-
staleCommandsRemoved++;
|
|
1911
|
-
}
|
|
1912
|
-
catch {
|
|
1913
|
-
// Doesn't exist — expected for most entries
|
|
1914
|
-
}
|
|
1915
|
-
}
|
|
1916
|
-
}
|
|
1917
|
-
// Sweep orphaned *-teams.md workflow command variants left by the Agent Teams
|
|
1918
|
-
// refactor. None are ever re-installed, so a blanket sweep is safe on any
|
|
1919
|
-
// install type (the full-install dir wipe only covers full installs).
|
|
1920
|
-
try {
|
|
1921
|
-
for (const f of await fs.readdir(commandsDir)) {
|
|
1922
|
-
if (f.endsWith('-teams.md')) {
|
|
1923
|
-
try {
|
|
1924
|
-
await fs.rm(path.join(commandsDir, f));
|
|
1925
|
-
staleCommandsRemoved++;
|
|
1926
|
-
}
|
|
1927
|
-
catch { /* already gone */ }
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
catch { /* commands dir absent — nothing to sweep */ }
|
|
1954
|
+
// Clean up stale commands (e.g., /review → /code-review) and rules from previous installations.
|
|
1955
|
+
// A folder the install refused, or that is a link, is not swept: nothing is read or removed through it.
|
|
1956
|
+
const { staleCommandsRemoved, staleRulesRemoved } = await sweepLegacyDevflowFiles({ claudeDir, report: installReport });
|
|
1932
1957
|
if (staleCommandsRemoved > 0 && verbose) {
|
|
1933
1958
|
p.log.info(`Cleaned up ${staleCommandsRemoved} legacy command(s)`);
|
|
1934
1959
|
}
|
|
1935
|
-
// Clean up stale rules from previous installations
|
|
1936
|
-
const rulesDir = path.join(claudeDir, 'rules', 'devflow');
|
|
1937
|
-
let staleRulesRemoved = 0;
|
|
1938
|
-
for (const legacy of LEGACY_RULE_NAMES) {
|
|
1939
|
-
const legacyPath = path.join(rulesDir, `${legacy}.md`);
|
|
1940
|
-
try {
|
|
1941
|
-
await fs.rm(legacyPath);
|
|
1942
|
-
staleRulesRemoved++;
|
|
1943
|
-
}
|
|
1944
|
-
catch {
|
|
1945
|
-
// Doesn't exist — expected for most entries
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
1960
|
if (staleRulesRemoved > 0 && verbose) {
|
|
1949
1961
|
p.log.info(`Cleaned up ${staleRulesRemoved} legacy rule(s)`);
|
|
1950
1962
|
}
|
|
@@ -2114,33 +2126,9 @@ export const initCommand = new Command('init')
|
|
|
2114
2126
|
// MUST run AFTER the proxy preflight block above: preflight can force proxyEnabled=false
|
|
2115
2127
|
// on failure, and reapply's dormancy (GPT models materialize only while proxy enabled)
|
|
2116
2128
|
// depends on the FINAL proxyEnabled value — running earlier would leave GPT model lines
|
|
2117
|
-
// in agent frontmatter after a preflight failure. Per-item failures are non-fatal.
|
|
2118
|
-
//
|
|
2119
|
-
|
|
2120
|
-
// An empty mapping means every agent uses its shipped default; the file copy already
|
|
2121
|
-
// wrote those defaults, so reapply would read ~34 files and write zero. The
|
|
2122
|
-
// disable/revert paths call reapplyAgentMapping directly and always need the full walk.
|
|
2123
|
-
{
|
|
2124
|
-
const agentInstallDir = path.join(claudeDir, 'agents', 'devflow');
|
|
2125
|
-
const preCheckMapping = await readAgentMapping(devflowDir);
|
|
2126
|
-
// hasAgentMappingEntries counts agent entries only: an agents.memory-only
|
|
2127
|
-
// mapping names no installed file, so it keeps this gate closed (D-WORKER-AGENTS).
|
|
2128
|
-
const hasMappingEntries = preCheckMapping.ok && hasAgentMappingEntries(preCheckMapping.value);
|
|
2129
|
-
if (hasMappingEntries || proxyEnabled) {
|
|
2130
|
-
const reapplyResult = await reapplyAgentMapping({
|
|
2131
|
-
proxyEnabled,
|
|
2132
|
-
installDir: agentInstallDir,
|
|
2133
|
-
devflowDir,
|
|
2134
|
-
onWarning: (msg) => { if (verbose)
|
|
2135
|
-
p.log.warn(msg); },
|
|
2136
|
-
});
|
|
2137
|
-
if (reapplyResult.updated.length > 0) {
|
|
2138
|
-
if (verbose) {
|
|
2139
|
-
p.log.info(`Agent model mapping reapplied: ${reapplyResult.updated.length} agent(s) updated`);
|
|
2140
|
-
}
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2129
|
+
// in agent frontmatter after a preflight failure. Per-item failures are non-fatal. The links
|
|
2130
|
+
// the walk met are printed below, through the install summary.
|
|
2131
|
+
const agentMappingLinks = await reapplyAgentMappingForInit({ proxyEnabled, claudeDir, devflowDir, verbose });
|
|
2144
2132
|
// Configure ambient hook, memory hooks, and HUD statusLine in a single read-modify-write pass
|
|
2145
2133
|
try {
|
|
2146
2134
|
let content = await fs.readFile(settingsPath, 'utf-8');
|
|
@@ -2437,6 +2425,10 @@ export const initCommand = new Command('init')
|
|
|
2437
2425
|
// Reference-overlay reporting: the overlay rewrites files inside an installed skill
|
|
2438
2426
|
// the user may have shadowed, and reports any unit it had to leave alone.
|
|
2439
2427
|
logSummaryLines(formatOverlaySummary(installReport));
|
|
2428
|
+
// A link the install or the agent model reapply replaced is a change to a path the user made.
|
|
2429
|
+
// A folder either refused is not refreshed, and looks refreshed on disk. A path the two steps
|
|
2430
|
+
// both met is named once.
|
|
2431
|
+
logSummaryLines(formatLinksSummary(mergeLinkReports(installReport, agentMappingLinks)));
|
|
2440
2432
|
// Skill-scoping reporting: a deselected skill is deleted and a dormant shadow
|
|
2441
2433
|
// is inert, and neither is distinguishable from "never installed" on disk.
|
|
2442
2434
|
//
|
|
@@ -2484,6 +2476,7 @@ export const initCommand = new Command('init')
|
|
|
2484
2476
|
}
|
|
2485
2477
|
// Write installation manifest for upgrade tracking (non-fatal — install already succeeded)
|
|
2486
2478
|
const now = new Date().toISOString();
|
|
2479
|
+
const installRecord = installRecordFor(variantConverge.converged, { curation: curationEnabled, compliance: complianceEnabled });
|
|
2487
2480
|
const manifestData = {
|
|
2488
2481
|
version,
|
|
2489
2482
|
// Resolved above, before the install, because the skills install set is
|
|
@@ -2525,6 +2518,9 @@ export const initCommand = new Command('init')
|
|
|
2525
2518
|
},
|
|
2526
2519
|
installedAt: existingManifest?.installedAt ?? now,
|
|
2527
2520
|
updatedAt: now,
|
|
2521
|
+
// The install record, written only after a converge that finished clean (AC-29): an
|
|
2522
|
+
// unclean one leaves the field out, which reads as unknown and names `devflow init`.
|
|
2523
|
+
...(installRecord !== null ? { installedVariants: installRecord } : {}),
|
|
2528
2524
|
};
|
|
2529
2525
|
// ── Manifest write + tracker selection lifecycle (the ONE call site) ──────
|
|
2530
2526
|
// persistManifestThenConvergeTracker owns the ordering invariant: the
|
|
@@ -2542,6 +2538,14 @@ export const initCommand = new Command('init')
|
|
|
2542
2538
|
else
|
|
2543
2539
|
p.log.info(msg.text);
|
|
2544
2540
|
}
|
|
2541
|
+
// The posting notice (AC-30): once, when the record moves from posting (compliance on, or no
|
|
2542
|
+
// record before this release) to non-posting. It follows the record write, so a record that
|
|
2543
|
+
// was withheld or never reached the disk leaves the notice due for the next init.
|
|
2544
|
+
if (trackerLifecycle.manifestWritten &&
|
|
2545
|
+
installRecord !== null &&
|
|
2546
|
+
postingNoticeDue(readInstalledVariants(existingManifest?.installedVariants), complianceEnabled)) {
|
|
2547
|
+
p.log.info(POSTING_OFF_NOTICE);
|
|
2548
|
+
}
|
|
2545
2549
|
// Only now that the machine-wide switch is on disk (D-INIT-DRAIN-AFTER-SWITCH).
|
|
2546
2550
|
const drainRoot = await lookupDrainLedgerRoot({ gitRoot, curationEnabled, manifestWritten: trackerLifecycle.manifestWritten });
|
|
2547
2551
|
if (drainRoot.warning !== null)
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import color from 'picocolors';
|
|
11
11
|
import { overlayUnitLabel } from '../../targets/claude-code/installer.js';
|
|
12
|
+
import { refusedFolderMessage, replacedLinkMessage } from '../../targets/claude-code/linked-folder.js';
|
|
12
13
|
import { SKILL_REFS_SKILL_NAME } from '../../core/mds-variants.js';
|
|
13
14
|
import { prefixSkillName } from '../../core/plugins.js';
|
|
14
15
|
/**
|
|
@@ -49,6 +50,39 @@ export function formatOverlaySummary(report, skillName = SKILL_REFS_SKILL_NAME)
|
|
|
49
50
|
}
|
|
50
51
|
return lines;
|
|
51
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Turn the replaced-link half of an InstallReport into summary lines, one per link.
|
|
55
|
+
*
|
|
56
|
+
* An install removes a link it finds where it keeps one of its own files or folders and writes a
|
|
57
|
+
* real file or folder there. What the link pointed to is not touched, which looks the same from the
|
|
58
|
+
* filesystem as an install that never met the link, so the line says both.
|
|
59
|
+
*
|
|
60
|
+
* Pure function — returns lines, logs nothing.
|
|
61
|
+
*/
|
|
62
|
+
export function formatReplacedLinksSummary(report) {
|
|
63
|
+
return report.replacedLinks.map(linkPath => ({ level: 'warn', message: replacedLinkMessage(linkPath) }));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Turn the refused-folder half of an InstallReport into summary lines, one per folder.
|
|
67
|
+
*
|
|
68
|
+
* An install writes nothing at a path that is a link it could not remove, or not a folder where it
|
|
69
|
+
* needs one, and leaves the entry as it was. That looks the same on disk as an install that refreshed
|
|
70
|
+
* the path, so the line says what was skipped and what to do about it.
|
|
71
|
+
*
|
|
72
|
+
* Pure function — returns lines, logs nothing.
|
|
73
|
+
*/
|
|
74
|
+
export function formatRefusedFoldersSummary(report) {
|
|
75
|
+
return report.refusedFolders.map(folder => ({ level: 'warn', message: refusedFolderMessage(folder) }));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Both halves in one list, the replaced links first: for a command that prints the links one
|
|
79
|
+
* converge met and has no other summary of its own.
|
|
80
|
+
*
|
|
81
|
+
* Pure function — returns lines, logs nothing.
|
|
82
|
+
*/
|
|
83
|
+
export function formatLinksSummary(report) {
|
|
84
|
+
return [...formatReplacedLinksSummary(report), ...formatRefusedFoldersSummary(report)];
|
|
85
|
+
}
|
|
52
86
|
/**
|
|
53
87
|
* The half of an overlay warning that describes what is actually on disk.
|
|
54
88
|
*
|