devflow-kit 3.2.0 → 3.4.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 +34 -0
- package/README.md +1 -1
- package/dist/agents/code.md +330 -0
- package/{src/assets → dist}/agents/design.md +11 -1
- package/{src/assets → dist}/agents/diagnose.md +17 -13
- package/dist/agents/git.md +29 -56
- package/{src/assets → dist}/agents/knowledge.md +8 -5
- package/{src/assets → dist}/agents/research.md +21 -2
- package/{src/assets → dist}/agents/review.md +18 -7
- package/{src/assets → dist}/agents/scrutinize.md +35 -11
- package/dist/agents/skim.md +148 -0
- package/{src/assets → dist}/agents/triage.md +8 -2
- package/dist/cli/commands/init.js +62 -0
- package/dist/cli/commands/learning.js +63 -8
- package/dist/cli/commands/uninstall.js +42 -1
- package/dist/commands/bug-analysis.md +30 -8
- package/dist/commands/code-review.md +142 -61
- package/dist/commands/debug.md +15 -13
- package/dist/commands/dynamic-build.md +176 -130
- package/dist/commands/dynamic-plan.md +31 -19
- package/dist/commands/dynamic-profile.md +27 -13
- package/dist/commands/dynamic-tickets.md +28 -14
- package/dist/commands/explore.md +16 -14
- package/dist/commands/implement.md +163 -158
- package/dist/commands/plan.md +37 -24
- package/dist/commands/release.md +69 -4
- package/dist/commands/research.md +33 -11
- package/dist/commands/resolve.md +61 -47
- package/dist/commands/self-review.md +50 -32
- package/dist/core/agent-models.js +53 -7
- package/dist/core/assets.js +55 -10
- package/dist/core/claude-md-audit.js +190 -0
- package/dist/core/feature-switch.js +20 -1
- package/dist/core/flags.js +28 -0
- package/dist/core/fs-atomic.js +8 -3
- package/dist/core/learning-tuning-config.js +8 -0
- package/dist/core/learning-variants.js +213 -0
- package/dist/core/manifest.js +62 -0
- package/dist/core/mds-variants.js +38 -1
- package/dist/core/plugins.js +78 -11
- package/{src/assets → dist/learning-off}/agents/code.md +77 -28
- package/dist/learning-off/agents/design.md +119 -0
- package/dist/learning-off/agents/diagnose.md +210 -0
- package/dist/learning-off/agents/knowledge.md +90 -0
- package/dist/learning-off/agents/research.md +149 -0
- package/dist/learning-off/agents/review.md +228 -0
- package/dist/learning-off/agents/scrutinize.md +117 -0
- package/{src/assets → dist/learning-off}/agents/skim.md +5 -10
- package/dist/learning-off/agents/triage.md +163 -0
- package/dist/learning-off/commands/bug-analysis.md +420 -0
- package/dist/learning-off/commands/code-review.md +525 -0
- package/dist/learning-off/commands/debug.md +294 -0
- package/dist/learning-off/commands/dynamic-build.md +1255 -0
- package/dist/learning-off/commands/dynamic-plan.md +424 -0
- package/dist/learning-off/commands/dynamic-profile.md +214 -0
- package/dist/learning-off/commands/dynamic-tickets.md +632 -0
- package/dist/learning-off/commands/explore.md +210 -0
- package/dist/learning-off/commands/implement.md +808 -0
- package/dist/learning-off/commands/plan.md +664 -0
- package/dist/learning-off/commands/release.md +310 -0
- package/dist/learning-off/commands/research.md +222 -0
- package/dist/learning-off/commands/resolve.md +837 -0
- package/dist/learning-off/commands/self-review.md +266 -0
- package/dist/skills/git/references/tracker/_contract.md +33 -0
- package/dist/skills/git/references/tracker/github/fetch-issue.md +2 -0
- package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +2 -0
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +5 -1
- package/dist/skills/git/references/tracker/github/post-wave-report.md +2 -0
- package/dist/skills/git/references/tracker/github/setup-task.md +12 -0
- package/dist/skills/git/references/tracker/jira/associate-release.md +1 -1
- package/dist/skills/git/references/tracker/jira/fetch-issue.md +2 -0
- package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +2 -0
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +5 -1
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +2 -0
- package/dist/skills/git/references/tracker/jira/setup-task.md +14 -2
- package/dist/skills/git/references/tracker/linear/associate-release.md +1 -1
- package/dist/skills/git/references/tracker/linear/fetch-issue.md +2 -0
- package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +2 -0
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +5 -1
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +2 -0
- package/dist/skills/git/references/tracker/linear/setup-task.md +14 -2
- package/dist/targets/claude-code/installer.js +72 -36
- package/dist/targets/claude-code/language-stamp.js +185 -0
- package/dist/targets/claude-code/learning-install.js +489 -0
- package/package.json +1 -1
- package/src/assets/agents/code.mds +339 -0
- package/src/assets/agents/design.mds +149 -0
- package/src/assets/agents/diagnose.mds +225 -0
- package/src/assets/agents/evaluate.md +12 -25
- package/src/assets/agents/git.mds +29 -56
- package/src/assets/agents/knowledge.mds +125 -0
- package/src/assets/agents/learning.md +4 -6
- package/src/assets/agents/research.mds +176 -0
- package/src/assets/agents/review.mds +286 -0
- package/src/assets/agents/scrutinize.mds +132 -0
- package/src/assets/agents/simplify.md +20 -0
- package/src/assets/agents/skim.mds +161 -0
- package/src/assets/agents/synthesize.md +12 -0
- package/src/assets/agents/test.md +2 -2
- package/src/assets/agents/triage.mds +194 -0
- package/src/assets/agents/validate.md +15 -8
- package/src/assets/commands/_partials/_compliance.mds +5 -4
- package/src/assets/commands/_partials/_decisions.mds +31 -0
- package/src/assets/commands/_partials/_engine.mds +29 -24
- package/src/assets/commands/_partials/_knowledge.mds +26 -13
- package/src/assets/commands/_partials/_plan_contract.mds +1 -1
- package/src/assets/commands/_partials/_preamble.mds +33 -9
- package/src/assets/commands/_partials/_publication.mds +5 -4
- package/src/assets/commands/_partials/_settings.mds +13 -5
- package/src/assets/commands/_partials/_wave.mds +16 -6
- package/src/assets/commands/bug-analysis.mds +24 -2
- package/src/assets/commands/code-review.mds +148 -45
- package/src/assets/commands/debug.mds +17 -1
- package/src/assets/commands/dynamic-build.mds +144 -65
- package/src/assets/commands/dynamic-plan.mds +36 -6
- package/src/assets/commands/dynamic-profile.mds +9 -1
- package/src/assets/commands/dynamic-tickets.mds +16 -2
- package/src/assets/commands/explore.mds +27 -1
- package/src/assets/commands/implement.mds +171 -138
- package/src/assets/commands/plan.mds +47 -8
- package/src/assets/commands/{release.md → release.mds} +27 -24
- package/src/assets/commands/research.mds +28 -4
- package/src/assets/commands/resolve.mds +69 -17
- package/src/assets/commands/self-review.mds +43 -13
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_contract.mds +72 -0
- package/src/assets/mds/tracker/_github.mds +13 -2
- package/src/assets/mds/tracker/_jira.mds +17 -5
- package/src/assets/mds/tracker/_linear.mds +17 -5
- package/src/assets/mds/tracker/_mcp.mds +2 -2
- package/src/assets/mds/tracker/_steps.mds +97 -0
- package/src/assets/rules/context-economy.md +10 -0
- package/src/assets/rules/go.md +1 -0
- package/src/assets/rules/java.md +1 -0
- package/src/assets/rules/python.md +1 -0
- package/src/assets/rules/rust.md +1 -0
- package/src/assets/rules/typescript.md +1 -0
- package/src/assets/scripts/ci-wait.cjs +636 -0
- package/src/assets/scripts/claude-md-audit.cjs +611 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +2 -3
- package/src/assets/scripts/hooks/background-memory-update +290 -9
- package/src/assets/scripts/hooks/json-helper.cjs +13 -5
- package/src/assets/scripts/hooks/json-parse +34 -10
- package/src/assets/scripts/hooks/session-start-context +315 -7
- package/src/assets/scripts/release-trace.cjs +27 -10
- package/src/assets/skills/accessibility/SKILL.md +1 -1
- package/src/assets/skills/apply-decisions/SKILL.md +12 -82
- package/src/assets/skills/apply-feature-knowledge/SKILL.md +9 -43
- package/src/assets/skills/architecture/SKILL.md +1 -1
- package/src/assets/skills/boundary-validation/SKILL.md +1 -1
- package/src/assets/skills/complexity/SKILL.md +1 -1
- package/src/assets/skills/compliance/SKILL.md +1 -1
- package/src/assets/skills/consistency/SKILL.md +1 -1
- package/src/assets/skills/database/SKILL.md +1 -1
- package/src/assets/skills/dependencies/SKILL.md +1 -1
- package/src/assets/skills/dependency-research/SKILL.md +3 -6
- package/src/assets/skills/design-review/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +1 -1
- package/src/assets/skills/documentation/SKILL.md +1 -1
- package/src/assets/skills/feature-knowledge/SKILL.md +43 -12
- package/src/assets/skills/gap-analysis/SKILL.md +1 -1
- package/src/assets/skills/git/SKILL.md +1 -1
- package/src/assets/skills/go/SKILL.md +1 -1
- package/src/assets/skills/java/SKILL.md +1 -1
- package/src/assets/skills/patterns/SKILL.md +1 -1
- package/src/assets/skills/performance/SKILL.md +1 -1
- package/src/assets/skills/python/SKILL.md +1 -1
- package/src/assets/skills/qa/SKILL.md +1 -3
- package/src/assets/skills/quality-gates/SKILL.md +10 -13
- package/src/assets/skills/quality-gates/references/report-template.md +20 -20
- package/src/assets/skills/react/SKILL.md +1 -1
- package/src/assets/skills/regression/SKILL.md +1 -1
- package/src/assets/skills/reliability/SKILL.md +1 -1
- package/src/assets/skills/research-codebase/SKILL.md +1 -1
- package/src/assets/skills/research-competitor/SKILL.md +1 -1
- package/src/assets/skills/research-external/SKILL.md +1 -1
- package/src/assets/skills/research-technology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/rust/SKILL.md +1 -1
- package/src/assets/skills/security/SKILL.md +1 -1
- package/src/assets/skills/software-design/SKILL.md +1 -1
- package/src/assets/skills/test-driven-development/SKILL.md +9 -29
- package/src/assets/skills/testing/SKILL.md +1 -1
- package/src/assets/skills/typescript/SKILL.md +1 -1
- package/src/assets/skills/ui-design/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/SKILL.md +3 -55
- package/src/assets/skills/worktree-support/references/discovery.md +48 -0
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
|
@@ -14,7 +14,7 @@ Run Simplify agent and Scrutinize agent sequentially on changed files for post-i
|
|
|
14
14
|
|
|
15
15
|
### Phase 0: Context Gathering
|
|
16
16
|
|
|
17
|
-
**Produces:** FILES_CHANGED, TASK_DESCRIPTION, DECISIONS_CONTEXT, FEATURE_KNOWLEDGE
|
|
17
|
+
**Produces:** FILES_CHANGED, TASK_DESCRIPTION, DECISIONS_CONTEXT, FEATURE_KNOWLEDGE, FEATURE_KNOWLEDGE_RULES, HEAD_BEFORE
|
|
18
18
|
|
|
19
19
|
Detect changed files and build context:
|
|
20
20
|
|
|
@@ -22,8 +22,22 @@ Detect changed files and build context:
|
|
|
22
22
|
2. Else run `git diff --name-only HEAD` + `git diff --name-only --cached` to get staged + unstaged
|
|
23
23
|
3. If no changes found, report "No changes to review" and exit
|
|
24
24
|
4. Build TASK_DESCRIPTION from recent commit messages or branch name
|
|
25
|
+
5. Record `HEAD_BEFORE` (`git rev-parse HEAD`) now, before Simplify runs — Phase 3 compares against it
|
|
26
|
+
|
|
27
|
+
**Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root when the run has one worktree:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> LEARNING=<on|off> KNOWLEDGE=<on|off>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=on LEARNING=on KNOWLEDGE=off` instead.
|
|
34
|
+
|
|
35
|
+
The accepted line is the only source of these values: the script alone folds the committed `.devflow/project.json`, the personal `.devflow/config.json` and the machine manifest.
|
|
36
|
+
|
|
25
37
|
### Load DECISIONS_CONTEXT
|
|
26
38
|
|
|
39
|
+
When the settings line says `LEARNING=off`, set `DECISIONS_CONTEXT` to `(none)` and skip this step, locating no ledger and reading no index.
|
|
40
|
+
|
|
27
41
|
The decisions ledger belongs to the repository, not to one checkout: in a linked worktree it lives in the main worktree, and a session started in a subdirectory reads the copy at the repository root. Locate it with ONE git call, run from the start directory — `WORKTREE_PATH` if provided, otherwise cwd (`devflow:worktree-support`):
|
|
28
42
|
|
|
29
43
|
```bash
|
|
@@ -81,26 +95,36 @@ Glob `{worktree}/.devflow/features/*/KNOWLEDGE.md`. For each file found, read on
|
|
|
81
95
|
|
|
82
96
|
Match the current task area and description against each index line (or frontmatter `description` + `directories` on fallback). Select entries whose documented area overlaps the current task. This is a relevance judgment — prefer specificity over breadth.
|
|
83
97
|
|
|
84
|
-
**Step 4 — Read selected
|
|
98
|
+
**Step 4 — Read each selected KB's Rules:**
|
|
99
|
+
|
|
100
|
+
For each selected entry, `{kb}` is `{worktree}/.devflow/features/{slug}/KNOWLEDGE.md`:
|
|
85
101
|
|
|
86
|
-
|
|
102
|
+
1. List its `##` headings with line numbers through Bash: `command grep -n '^## ' "{kb}"`. This only locates sections; the text of a KB comes from the Read view alone.
|
|
103
|
+
2. Read the `## Rules` range (its line to the next heading) with the Read tool, using `offset` and `limit`, and choose the one to three bullets most relevant to the current task. The choice is yours, made per KB.
|
|
104
|
+
3. If the KB has no `## Rules` section, choose one to three entries from its `## Anti-Patterns` or `## Gotchas` range the same way, and label them by that section's name instead of an ID.
|
|
87
105
|
|
|
88
|
-
**
|
|
106
|
+
When a KB contradicts the code you observe, **trust the code** — the code is the freshness mechanism; the KB may lag behind. A missing Rules section, a missing heading list and `(none)` are legitimate states, not errors.
|
|
89
107
|
|
|
90
|
-
|
|
108
|
+
**Step 5 — Set FEATURE_KNOWLEDGE and FEATURE_KNOWLEDGE_RULES:**
|
|
109
|
+
|
|
110
|
+
Write one block per selected KB. Paste each bullet verbatim from the Read view, never from a shell view. The path is relative to the checkout root; an agent resolves it under `WORKTREE_PATH` when one is provided.
|
|
91
111
|
|
|
92
112
|
```
|
|
93
113
|
--- Feature knowledge: {slug} ---
|
|
94
|
-
{
|
|
114
|
+
KB: .devflow/features/{slug}/KNOWLEDGE.md
|
|
115
|
+
Rules:
|
|
116
|
+
- **KB-AP-2** {bullet text, verbatim}
|
|
117
|
+
- **KB-INV-1** {bullet text, verbatim}
|
|
118
|
+
Headings: L5 Rules · L40 Overview · L62 Anti-Patterns · L118 Key Files
|
|
95
119
|
```
|
|
96
120
|
|
|
97
|
-
If no KBs exist, no KBs are relevant, or `.devflow/features/` is absent, set
|
|
121
|
+
A KB with no Rules section labels its entries `Rules ({section name}):` and gives them no ID. `FEATURE_KNOWLEDGE` is these blocks; `FEATURE_KNOWLEDGE_RULES` is the same blocks without the `Headings:` line. Both come from this one selection, and each spawn names the variable its recipient takes. If no KBs exist, no KBs are relevant, or `.devflow/features/` is absent, set both to `(none)`.
|
|
98
122
|
|
|
99
|
-
**One git call, then direct
|
|
123
|
+
**One git call, then direct reads — no `.cjs` script.** After resolving `{worktree}`, this step is 1 index read (or N frontmatter reads on fallback), plus one heading listing and one Rules read per selected KB, bounded by KB count.
|
|
100
124
|
|
|
101
|
-
Pass `
|
|
125
|
+
Pass `FEATURE_KNOWLEDGE_RULES` to Scrutinize agent.
|
|
102
126
|
|
|
103
|
-
**Extract:** FILES_CHANGED (list), TASK_DESCRIPTION (string), DECISIONS_CONTEXT (string, optional), FEATURE_KNOWLEDGE (string, optional)
|
|
127
|
+
**Extract:** FILES_CHANGED (list), TASK_DESCRIPTION (string), DECISIONS_CONTEXT (string, optional), FEATURE_KNOWLEDGE (string, optional), FEATURE_KNOWLEDGE_RULES (string, optional), HEAD_BEFORE (40-hex SHA)
|
|
104
128
|
|
|
105
129
|
### Phase 1: Simplify agent (Code Refinement)
|
|
106
130
|
|
|
@@ -112,13 +136,14 @@ Spawn Simplify agent to refine code for clarity and consistency:
|
|
|
112
136
|
Agent(subagent_type="Simplify", run_in_background=false):
|
|
113
137
|
"TASK_DESCRIPTION: {task_description}
|
|
114
138
|
FILES_CHANGED: {files_changed}
|
|
115
|
-
Simplify and refine the code for clarity and consistency while preserving functionality.
|
|
139
|
+
Simplify and refine the code for clarity and consistency while preserving functionality.
|
|
140
|
+
Commit any improvements with a conventional-commit message."
|
|
116
141
|
|
|
117
142
|
**Wait for completion.** Simplify agent commits changes directly.
|
|
118
143
|
|
|
119
144
|
### Phase 2: Scrutinize agent (9-Pillar Quality Gate)
|
|
120
145
|
|
|
121
|
-
**Produces:** SCRUTINIZE_STATUS
|
|
146
|
+
**Produces:** SCRUTINIZE_STATUS
|
|
122
147
|
**Requires:** FILES_CHANGED, TASK_DESCRIPTION, DECISIONS_CONTEXT
|
|
123
148
|
|
|
124
149
|
Spawn Scrutinize agent for quality evaluation and fixing:
|
|
@@ -127,22 +152,24 @@ Agent(subagent_type="Scrutinize", run_in_background=false):
|
|
|
127
152
|
"TASK_DESCRIPTION: {task_description}
|
|
128
153
|
FILES_CHANGED: {files_changed}
|
|
129
154
|
DECISIONS_CONTEXT: {decisions_context}
|
|
130
|
-
FEATURE_KNOWLEDGE: {
|
|
155
|
+
FEATURE_KNOWLEDGE: {feature_knowledge_rules}
|
|
131
156
|
Evaluate against 9-pillar framework. Fix P0/P1 issues. Return structured report.
|
|
132
157
|
Follow devflow:apply-decisions to scan DECISIONS_CONTEXT and Read full ADR/PF bodies on demand. Skip if (none).
|
|
133
158
|
Follow devflow:apply-feature-knowledge for FEATURE_KNOWLEDGE. Skip if (none)."
|
|
134
159
|
|
|
135
|
-
**Wait for completion.** Extract: STATUS (PASS|FIXED|BLOCKED)
|
|
160
|
+
**Wait for completion.** Extract: STATUS (PASS|FIXED|BLOCKED) from the `### Status` line
|
|
136
161
|
|
|
137
162
|
### Phase 3: Conditional Validation
|
|
138
163
|
|
|
139
164
|
**Produces:** VALIDATION_RESULT
|
|
140
|
-
**Requires:**
|
|
165
|
+
**Requires:** HEAD_BEFORE, SCRUTINIZE_STATUS
|
|
166
|
+
|
|
167
|
+
If STATUS is BLOCKED, skip this phase: the report (Phase 4) names the blocker.
|
|
141
168
|
|
|
142
|
-
|
|
169
|
+
Run this phase only if HEAD moved — `git rev-parse HEAD` differs from `HEAD_BEFORE`, meaning Simplify or Scrutinize committed (Simplify alone counts, whatever Scrutinize's STATUS is). Otherwise skip it. Its scope is changed-only over what the two agents changed:
|
|
143
170
|
|
|
144
171
|
Agent(subagent_type="Validate", run_in_background=false):
|
|
145
|
-
"FILES_CHANGED: {
|
|
172
|
+
"FILES_CHANGED: {output of `git diff --name-only HEAD_BEFORE..HEAD`}
|
|
146
173
|
VALIDATION_SCOPE: changed-only
|
|
147
174
|
Run build, typecheck, lint, test on modified files"
|
|
148
175
|
|
|
@@ -191,24 +218,14 @@ git -C "{start}" rev-parse --show-toplevel
|
|
|
191
218
|
|
|
192
219
|
and use its one-line output. If the command fails (outside a git repository), `{worktree}` is the start directory itself. All paths below are relative to `{worktree}`.
|
|
193
220
|
|
|
194
|
-
**Step 1 — Check the opt-out gate, with `{root}` = `{worktree}`:**
|
|
195
|
-
|
|
196
|
-
**Resolve the settings line** once per worktree root, reusing a line this run already resolved for the same root. `{root}` is the worktree the values are for — the repository root when the run has one worktree:
|
|
197
|
-
|
|
198
|
-
```bash
|
|
199
|
-
node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> LEARNING=<on|off> KNOWLEDGE=<on|off>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=on LEARNING=on KNOWLEDGE=off` instead.
|
|
203
|
-
|
|
204
|
-
The accepted line is the only source of these values: the script alone folds the committed `.devflow/project.json`, the personal `.devflow/config.json` and the machine manifest.
|
|
221
|
+
**Step 1 — Check the opt-out gate, with `{root}` = `{worktree}`:** take the settings line resolved above for that root, resolving it with the settings block when this run has not yet.
|
|
205
222
|
|
|
206
223
|
If the settings line says `KNOWLEDGE=off`, skip write-back entirely. The machine switch (`devflow knowledge --disable`), the repository and the personal settings can each turn knowledge off, and none can turn it back on (D-FEATURES-NARROW-ONLY). The fail-closed line says `KNOWLEDGE=off` too, so an unresolvable line skips write-back.
|
|
207
224
|
|
|
208
225
|
**Step 2 — Evaluate whether write-back is warranted:**
|
|
209
226
|
|
|
210
227
|
Only proceed if **at least one** of these is true:
|
|
211
|
-
- This workflow changed files in a directory that is documented by an existing feature knowledge base (a documented area changed).
|
|
228
|
+
- This workflow changed files in a directory that is documented by an existing feature knowledge base (a documented area changed). Knowledge bases are written through at that point, never on a background schedule.
|
|
212
229
|
- This workflow surfaced durable, cross-cutting knowledge about a codebase area that would help future agents working in the same area — patterns, anti-patterns, integration points, gotchas not visible from a single file read.
|
|
213
230
|
|
|
214
231
|
**Never spawn unconditionally.** If neither condition is met, skip write-back silently.
|
|
@@ -231,7 +248,7 @@ Write the knowledge base to:
|
|
|
231
248
|
Then update the index cache by performing a read-modify-write on:
|
|
232
249
|
{worktree}/.devflow/features/index.md
|
|
233
250
|
|
|
234
|
-
Index line format: `- **{slug}** — {areas} — {Use-when description}`
|
|
251
|
+
Index line format: `- **{slug}** — {areas} — {Use-when description}` — at most 300 characters, the description at most 220; reword a longer one, never cut it.
|
|
235
252
|
|
|
236
253
|
If the line for this slug already exists in index.md, replace it. If it does not exist, append it. If index.md does not exist, create it with just this line.
|
|
237
254
|
|
|
@@ -252,6 +269,7 @@ When the Knowledge agent reports `KB_COMMIT: skipped (detached HEAD)`, the files
|
|
|
252
269
|
│
|
|
253
270
|
├─ Phase 0: Context gathering
|
|
254
271
|
│ ├─ Git diff for changed files
|
|
272
|
+
│ ├─ Record HEAD_BEFORE
|
|
255
273
|
│ └─ Read index.md → DECISIONS_CONTEXT
|
|
256
274
|
│
|
|
257
275
|
├─ Phase 1: Simplify agent
|
|
@@ -261,7 +279,7 @@ When the Knowledge agent reports `KB_COMMIT: skipped (detached HEAD)`, the files
|
|
|
261
279
|
│ └─ 9-pillar quality gate (may fix and commit)
|
|
262
280
|
│
|
|
263
281
|
├─ Phase 3: Validate agent (conditional)
|
|
264
|
-
│ └─ If
|
|
282
|
+
│ └─ If HEAD moved since HEAD_BEFORE (Simplify or Scrutinize committed), changed-only Validate over HEAD_BEFORE..HEAD
|
|
265
283
|
│
|
|
266
284
|
└─ Phase 4: Report
|
|
267
285
|
└─ Display summary with pillar status
|
|
@@ -280,6 +298,6 @@ When the Knowledge agent reports `KB_COMMIT: skipped (detached HEAD)`, the files
|
|
|
280
298
|
|
|
281
299
|
1. **Orchestration only** - Command spawns agents, doesn't do the work
|
|
282
300
|
2. **Sequential execution** - Simplify agent must complete before Scrutinize agent
|
|
283
|
-
3. **Validation gate** - If Scrutinize agent changes
|
|
301
|
+
3. **Validation gate** - If Simplify agent or Scrutinize agent committed, the changes must pass validation
|
|
284
302
|
4. **Honest reporting** - Display actual agent outputs
|
|
285
303
|
5. **Fail fast** - Stop on BLOCKED or validation failure
|
|
@@ -83,13 +83,16 @@ export const EFFORT_INHERIT = 'inherit';
|
|
|
83
83
|
* count are unaffected.
|
|
84
84
|
*
|
|
85
85
|
* Shipped value of `memory`: `claude-sonnet-5-5` at `high` effort. User decision
|
|
86
|
-
* 2026-10-08: memory quality is worth more than the cost saving of Haiku
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
86
|
+
* 2026-10-08: memory quality is worth more than the cost saving of Haiku.
|
|
87
|
+
*
|
|
88
|
+
* D-MEMORY-WORKER-LEAN: background-memory-update reads `agents.memory` from
|
|
89
|
+
* `~/.devflow/agent-models.json` at run time, validating `model` and `effort`
|
|
90
|
+
* field by field against the domain below, and passes `--effort` when the CLI
|
|
91
|
+
* version probe puts it at or above the lean-argv floor. A field it cannot use
|
|
92
|
+
* falls back to this row. The hook spells that fallback out for bash
|
|
93
|
+
* (`MEMORY_MODEL_DEFAULT`, `MEMORY_EFFORT_DEFAULT`, the alias and effort lists and
|
|
94
|
+
* the model-name rules), and tests/agent-models-worker.test.ts holds every one
|
|
95
|
+
* equal to this row and to the domain, so the two cannot drift apart.
|
|
93
96
|
*/
|
|
94
97
|
export const WORKER_AGENTS = {
|
|
95
98
|
memory: { model: 'claude-sonnet-5-5', effort: 'high' },
|
|
@@ -596,6 +599,49 @@ export async function loadShippedAgentDefaults(dirs = agentSourceDirs(), opts) {
|
|
|
596
599
|
}
|
|
597
600
|
return defaults;
|
|
598
601
|
}
|
|
602
|
+
// ---------------------------------------------------------------------------
|
|
603
|
+
// carryAgentOverrides — the frontmatter a converge keeps
|
|
604
|
+
// ---------------------------------------------------------------------------
|
|
605
|
+
/**
|
|
606
|
+
* D-AGENT-OVERRIDE-CARRY: the text a prompt converge installs for an agent is the
|
|
607
|
+
* variant `source` wearing the installed copy's `model:` and `effort:`.
|
|
608
|
+
*
|
|
609
|
+
* Those two lines are the only frontmatter keys reapplyAgentMapping manages, and
|
|
610
|
+
* `devflow agents` and the proxy put their values there. A converge that installed
|
|
611
|
+
* the bare variant would write every overridden agent back to its shipped model
|
|
612
|
+
* and effort and leave reapplyAgentMapping to restore it: a window with default
|
|
613
|
+
* models, and a no-op converge that rewrites everything. Carrying the two lines
|
|
614
|
+
* keeps that window shut, so a copy already holding the right variant and its
|
|
615
|
+
* overrides compares byte-equal and is not touched.
|
|
616
|
+
*
|
|
617
|
+
* The carry is an optimisation, never the authority: reapplyAgentMapping reads
|
|
618
|
+
* agent-models.json and decides what the installed frontmatter must say. It
|
|
619
|
+
* agrees with the carry whenever the installed copy was last written by it, and
|
|
620
|
+
* where they disagree the later reapply wins. Both go through
|
|
621
|
+
* rewriteAgentFrontmatter, so the model-name charset guard, the first-block scope
|
|
622
|
+
* and the EOL handling are the ones the reapply uses.
|
|
623
|
+
*
|
|
624
|
+
* `source` is returned unchanged when there is nothing safe to carry: either side
|
|
625
|
+
* has no well-formed frontmatter, the installed copy has no model, its model
|
|
626
|
+
* fails the model-name charset or its effort is not an EFFORT_LEVELS member. An
|
|
627
|
+
* installed copy with no `effort:` line carries no effort (a mapping effort of
|
|
628
|
+
* `inherit` yields exactly that).
|
|
629
|
+
*
|
|
630
|
+
* Pure function — no I/O.
|
|
631
|
+
*/
|
|
632
|
+
export function carryAgentOverrides(source, installed) {
|
|
633
|
+
const model = readFrontmatterModel(installed);
|
|
634
|
+
const effort = readFrontmatterEffort(installed);
|
|
635
|
+
if (!model.ok || !effort.ok || model.value === '')
|
|
636
|
+
return source;
|
|
637
|
+
if (effort.value !== '' && !isEffortLevel(effort.value))
|
|
638
|
+
return source;
|
|
639
|
+
const carried = rewriteAgentFrontmatter(source, {
|
|
640
|
+
model: model.value,
|
|
641
|
+
effort: effort.value === '' ? null : effort.value,
|
|
642
|
+
});
|
|
643
|
+
return carried.ok ? carried.value.content : source;
|
|
644
|
+
}
|
|
599
645
|
/**
|
|
600
646
|
* Idempotent convergence function: walk every installed agent file and
|
|
601
647
|
* rewrite frontmatter model/effort to match the effective mapping.
|
package/dist/core/assets.js
CHANGED
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { join } from 'path';
|
|
2
2
|
import { getPackageRoot } from './paths.js';
|
|
3
3
|
import { SKILL_REFS_OUTPUT_DIR } from './mds-variants.js';
|
|
4
|
+
import { LEARNING_OFF_OUTPUT_DIR } from './learning-variants.js';
|
|
4
5
|
/**
|
|
5
6
|
* Flat skills source directory: src/assets/skills/{name}/
|
|
6
7
|
* All plugins' skills live here directly (no per-plugin subdirectory).
|
|
8
|
+
*
|
|
9
|
+
* @param root - Package root to resolve against. Injectable so a caller working
|
|
10
|
+
* on a temp tree (the learning converge) reads the skill source from the same
|
|
11
|
+
* root as the rest of its sources.
|
|
7
12
|
*/
|
|
8
|
-
export function skillsDir() {
|
|
9
|
-
return join(
|
|
13
|
+
export function skillsDir(root = getPackageRoot()) {
|
|
14
|
+
return join(root, 'src', 'assets', 'skills');
|
|
10
15
|
}
|
|
11
16
|
/**
|
|
12
|
-
* Flat agents source directory: src/assets/agents/{name}.md
|
|
13
|
-
* All plugins' agents live here directly.
|
|
17
|
+
* Flat agents source directory: src/assets/agents/{name}.md, or {name}.mds for a
|
|
18
|
+
* generator host. All plugins' agents live here directly.
|
|
14
19
|
*
|
|
15
20
|
* @param root - Package root to resolve against. Injectable so a caller working
|
|
16
21
|
* on a temp tree (the test harness) reads the layout from here rather than
|
|
@@ -35,17 +40,49 @@ export function scriptsDir() {
|
|
|
35
40
|
}
|
|
36
41
|
/**
|
|
37
42
|
* Compiled commands directory: dist/commands/
|
|
38
|
-
* Single lookup directory for installed commands
|
|
39
|
-
*
|
|
43
|
+
* Single lookup directory for installed commands: one compiled file per `.mds`
|
|
44
|
+
* command host in src/assets/commands/. A host that carries a learning arm also
|
|
45
|
+
* has a learning-off variant (see learningOffDir).
|
|
40
46
|
*/
|
|
41
47
|
export function commandsDir() {
|
|
42
48
|
return join(getPackageRoot(), 'dist', 'commands');
|
|
43
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Learning-off variant directory: dist/learning-off/{commands,agents}/
|
|
52
|
+
*
|
|
53
|
+
* Holds the learning-off variant of each prompt that carries a learning arm
|
|
54
|
+
* (D-LEARNING-VARIANTS), and only those. Absent until the build has run, so every
|
|
55
|
+
* reader tolerates its absence.
|
|
56
|
+
*
|
|
57
|
+
* The spelling comes from LEARNING_OFF_OUTPUT_DIR in learning-variants.ts, the
|
|
58
|
+
* build's own destination, rather than being retyped here.
|
|
59
|
+
*
|
|
60
|
+
* @param kind - Which prompt kind's variants.
|
|
61
|
+
* @param root - Package root to resolve against (see agentsDir).
|
|
62
|
+
*/
|
|
63
|
+
export function learningOffDir(kind, root = getPackageRoot()) {
|
|
64
|
+
return join(root, ...LEARNING_OFF_OUTPUT_DIR.split('/'), kind);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Command source directories, MOST-PREFERRED FIRST.
|
|
68
|
+
*
|
|
69
|
+
* D-LEARNING-VARIANT-INSTALL: with learning off the learning-off variant comes
|
|
70
|
+
* first, so a host with an arm installs its off variant and a host with none falls
|
|
71
|
+
* through to the one compiled file; with learning on the order is the normal
|
|
72
|
+
* single-directory lookup.
|
|
73
|
+
*
|
|
74
|
+
* @param learning - The machine's settled learning switch.
|
|
75
|
+
* @param root - Package root to resolve against (see agentsDir).
|
|
76
|
+
*/
|
|
77
|
+
export function commandSourceDirs(learning, root = getPackageRoot()) {
|
|
78
|
+
const normal = join(root, 'dist', 'commands');
|
|
79
|
+
return learning ? [normal] : [learningOffDir('commands', root), normal];
|
|
80
|
+
}
|
|
44
81
|
/**
|
|
45
82
|
* Compiled agents directory: dist/agents/{name}.md
|
|
46
83
|
*
|
|
47
|
-
* Output of the .mds generator hosts. The directory is absent until
|
|
48
|
-
*
|
|
84
|
+
* Output of the .mds generator hosts. The directory is absent until the build has
|
|
85
|
+
* run, so every reader must tolerate its absence.
|
|
49
86
|
*
|
|
50
87
|
* @param root - Package root to resolve against (see agentsDir).
|
|
51
88
|
*/
|
|
@@ -87,9 +124,17 @@ export function compiledSkillRefsDir(root = getPackageRoot()) {
|
|
|
87
124
|
* The non-empty tuple makes an empty list a compile error at every call site:
|
|
88
125
|
* an empty list would survive a `??` default and resolve to nothing.
|
|
89
126
|
*
|
|
127
|
+
* D-LEARNING-VARIANT-INSTALL: `learning: false` puts dist/learning-off/agents first,
|
|
128
|
+
* for the installer and the converge only. The default (learning on) is the order
|
|
129
|
+
* every other reader wants: the shipped defaults (model, effort) are the same in
|
|
130
|
+
* both variants, so loadShippedAgentDefaults and the test harness keep reading the
|
|
131
|
+
* learning-on tree.
|
|
132
|
+
*
|
|
90
133
|
* @param root - Package root to resolve against (see agentsDir).
|
|
134
|
+
* @param learning - The machine's settled learning switch; on by default.
|
|
91
135
|
*/
|
|
92
|
-
export function agentSourceDirs(root = getPackageRoot()) {
|
|
93
|
-
|
|
136
|
+
export function agentSourceDirs(root = getPackageRoot(), learning = true) {
|
|
137
|
+
const normal = [compiledAgentsDir(root), agentsDir(root)];
|
|
138
|
+
return learning ? normal : [learningOffDir('agents', root), ...normal];
|
|
94
139
|
}
|
|
95
140
|
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's view of the CLAUDE.md import audit — a typed seam onto the package's
|
|
3
|
+
* own `claude-md-audit.cjs`, never a second implementation of it.
|
|
4
|
+
*
|
|
5
|
+
* D-CLAUDE-MD-IMPORT-AUDIT: the logic (import grammar, bounds, thresholds, display
|
|
6
|
+
* text, stamp format) lives once, in src/assets/scripts/claude-md-audit.cjs, because
|
|
7
|
+
* the SessionStart hook reaches only `~/.devflow/scripts/`, which `composeScripts`
|
|
8
|
+
* fills by copying `src/assets/scripts/` verbatim. This module is the typed facade
|
|
9
|
+
* `devflow init` calls, loaded with the evidence-policy seam's `loadScript` against
|
|
10
|
+
* an explicit surface map (D-POLICY-CJS-SEAM). The interfaces below are TRANSCRIBED
|
|
11
|
+
* from the script's JSDoc and are this side's only shape authority; the module is
|
|
12
|
+
* required from `scriptsDir()` (the package's own copy), never from the installed
|
|
13
|
+
* `~/.devflow/scripts`, which may be older than this CLI.
|
|
14
|
+
*
|
|
15
|
+
* Nothing here throws: a load failure, a script that throws and a stamp that cannot
|
|
16
|
+
* be written are `Result` errors, and init prints at most one degraded line for them
|
|
17
|
+
* and keeps its exit code.
|
|
18
|
+
*
|
|
19
|
+
* D-AUDIT-STAMP (the caller's half): the audit writes nothing; this module writes the
|
|
20
|
+
* stamp after the install, only under an existing machine root (a command never
|
|
21
|
+
* creates `~/.devflow` for it), only through a sibling temp file renamed into place
|
|
22
|
+
* ({@link writeFileAtomicExclusive}), and never through a link. Because
|
|
23
|
+
* `writeFileAtomicExclusive` does not refuse a symlink at the target, the write does
|
|
24
|
+
* its own `lstat` regular-file check first (AC-437): a stamp path that is a symbolic
|
|
25
|
+
* link or any other non-regular file is refused and nothing is written.
|
|
26
|
+
*/
|
|
27
|
+
import { promises as fs } from 'fs';
|
|
28
|
+
import { join } from 'path';
|
|
29
|
+
import { scriptsDir } from './assets.js';
|
|
30
|
+
import { writeFileAtomicExclusive } from './fs-atomic.js';
|
|
31
|
+
import { loadScript, } from './evidence-policy.js';
|
|
32
|
+
/** The script, relative to src/assets/scripts/ (and ~/.devflow/scripts/). */
|
|
33
|
+
export const CLAUDE_MD_AUDIT_SCRIPT_NAME = 'claude-md-audit.cjs';
|
|
34
|
+
/** The stamp's basename under the machine root (`~/.devflow`). */
|
|
35
|
+
export const CLAUDE_MD_AUDIT_STAMP_FILE = '.claude-md-audit';
|
|
36
|
+
/**
|
|
37
|
+
* The prefix of the stamp's sibling temp file, `<stamp>.tmp.<pid>`: the one name
|
|
38
|
+
* the hook and {@link writeFileAtomicExclusive} both create, so a crash between the
|
|
39
|
+
* write and the rename leaves a name uninstall can sweep.
|
|
40
|
+
*/
|
|
41
|
+
export const CLAUDE_MD_AUDIT_STAMP_TMP_PREFIX = `${CLAUDE_MD_AUDIT_STAMP_FILE}.tmp.`;
|
|
42
|
+
/** The stamp is created owner-only, the mode the hook's `umask 077` write gives it. */
|
|
43
|
+
const STAMP_CREATE_MODE = 0o600;
|
|
44
|
+
/** Every key of ClaudeMdAuditModule and the runtime kind the loader requires of it. */
|
|
45
|
+
export const CLAUDE_MD_AUDIT_MODULE_SURFACE = Object.freeze({
|
|
46
|
+
FILE_THRESHOLD_BYTES: 'number',
|
|
47
|
+
CHAIN_THRESHOLD_BYTES: 'number',
|
|
48
|
+
MAX_HOPS: 'number',
|
|
49
|
+
MAX_PATHS_PER_ROOT: 'number',
|
|
50
|
+
SCAN_BYTES: 'number',
|
|
51
|
+
MAX_BYTES_READ: 'number',
|
|
52
|
+
MAX_STAMP_BYTES: 'number',
|
|
53
|
+
MAX_KEYS: 'number',
|
|
54
|
+
SKIP_FILE_BYTES: 'number',
|
|
55
|
+
NOT_SHOWN: 'string',
|
|
56
|
+
HOOK_MAGIC: 'string',
|
|
57
|
+
HOOK_END: 'string',
|
|
58
|
+
extractImports: 'function',
|
|
59
|
+
isDisplayable: 'function',
|
|
60
|
+
isRecordable: 'function',
|
|
61
|
+
formatFinding: 'function',
|
|
62
|
+
findingKey: 'function',
|
|
63
|
+
parseStamp: 'function',
|
|
64
|
+
renderStamp: 'function',
|
|
65
|
+
readStampFile: 'function',
|
|
66
|
+
audit: 'function',
|
|
67
|
+
main: 'function',
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Load the audit script from `dir` (default: the package's own scripts directory)
|
|
71
|
+
* and shape-check its surface. Never throws.
|
|
72
|
+
*/
|
|
73
|
+
export function loadClaudeMdAuditModule(dir = scriptsDir()) {
|
|
74
|
+
return loadScript(join(dir, CLAUDE_MD_AUDIT_SCRIPT_NAME), CLAUDE_MD_AUDIT_MODULE_SURFACE);
|
|
75
|
+
}
|
|
76
|
+
// ── Roots ──────────────────────────────────────────────────────────────────────
|
|
77
|
+
/**
|
|
78
|
+
* The gated root set, in the order the hook passes it: `CLAUDE.md` in the Claude
|
|
79
|
+
* config directory, then (only for a git project that is not HOME — the caller
|
|
80
|
+
* passes `null` otherwise) the project's `CLAUDE.md`, `.claude/CLAUDE.md` and
|
|
81
|
+
* `CLAUDE.local.md` at its toplevel. Ancestor directories, lazily loaded
|
|
82
|
+
* subdirectory files and `rules/*.md` are not roots.
|
|
83
|
+
*/
|
|
84
|
+
export function claudeMdAuditRoots(claudeDir, projectRoot) {
|
|
85
|
+
const roots = [join(claudeDir, 'CLAUDE.md')];
|
|
86
|
+
if (projectRoot !== null) {
|
|
87
|
+
roots.push(join(projectRoot, 'CLAUDE.md'), join(projectRoot, '.claude', 'CLAUDE.md'), join(projectRoot, 'CLAUDE.local.md'));
|
|
88
|
+
}
|
|
89
|
+
return roots;
|
|
90
|
+
}
|
|
91
|
+
/** The stamp's text when it is a regular file of at most MAX_STAMP_BYTES, else null (read as absent). */
|
|
92
|
+
export async function readClaudeMdAuditStamp(devflowDir, maxBytes) {
|
|
93
|
+
const stampPath = join(devflowDir, CLAUDE_MD_AUDIT_STAMP_FILE);
|
|
94
|
+
try {
|
|
95
|
+
const st = await fs.lstat(stampPath);
|
|
96
|
+
if (!st.isFile() || st.size > maxBytes)
|
|
97
|
+
return null;
|
|
98
|
+
return await fs.readFile(stampPath, 'utf-8');
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Run the audit over the gated roots. Reads only; the stamp is written by
|
|
106
|
+
* {@link writeClaudeMdAuditStamp}. Never throws.
|
|
107
|
+
*/
|
|
108
|
+
export async function runClaudeMdAudit(opts) {
|
|
109
|
+
const loaded = loadClaudeMdAuditModule(opts.scriptsDir);
|
|
110
|
+
if (!loaded.ok)
|
|
111
|
+
return { ok: false, error: { kind: 'load', detail: loaded.error } };
|
|
112
|
+
const audit = loaded.value;
|
|
113
|
+
try {
|
|
114
|
+
const stampText = await readClaudeMdAuditStamp(opts.devflowDir, audit.MAX_STAMP_BYTES);
|
|
115
|
+
const prior = stampText === null ? [] : audit.parseStamp(stampText).keys;
|
|
116
|
+
const roots = claudeMdAuditRoots(opts.claudeDir, opts.projectRoot);
|
|
117
|
+
if (opts.showAll !== true)
|
|
118
|
+
return { ok: true, value: audit.audit({ roots, home: opts.home, keys: prior }) };
|
|
119
|
+
const result = audit.audit({ roots, home: opts.home, keys: [] });
|
|
120
|
+
const keys = [...prior.filter(key => !result.shown.includes(key)), ...result.shown].slice(-audit.MAX_KEYS);
|
|
121
|
+
return { ok: true, value: { ...result, stamp: audit.renderStamp({ roots, examined: result.examined, keys }) } };
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
return { ok: false, error: { kind: 'failed', detail: err instanceof Error ? err.message : String(err) } };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Run the audit and record its stamp (D-AUDIT-STAMP): what `devflow init` does after the
|
|
129
|
+
* install. Every finding over a threshold is displayed (`showAll`), as init is a deliberate
|
|
130
|
+
* command. The stamp carries the examined paths and every key the audit displayed, so the
|
|
131
|
+
* first SessionStart afterwards repeats nothing it was just shown. A stamp that cannot be
|
|
132
|
+
* written (no machine root, a link in the way, a full disk) costs a repeated message at
|
|
133
|
+
* most, never the audit's result, so that failure is not reported. Never throws.
|
|
134
|
+
*/
|
|
135
|
+
export async function auditAndRecordClaudeMd(opts) {
|
|
136
|
+
const run = await runClaudeMdAudit({ ...opts, showAll: true });
|
|
137
|
+
if (run.ok)
|
|
138
|
+
await writeClaudeMdAuditStamp(opts.devflowDir, run.value.stamp);
|
|
139
|
+
return run;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The display lines of an audit result, as ONE note body, or null when nothing is
|
|
143
|
+
* flagged. The single formatter both init paths (Recommended and Advanced) use; the
|
|
144
|
+
* lines themselves are produced by the script's own formatter, the one the hook
|
|
145
|
+
* shows in its systemMessage.
|
|
146
|
+
*/
|
|
147
|
+
export function formatClaudeMdAuditNote(result) {
|
|
148
|
+
return result.lines.length === 0 ? null : result.lines.join('\n');
|
|
149
|
+
}
|
|
150
|
+
/** The one degraded line printed when the audit could not run. */
|
|
151
|
+
export function formatClaudeMdAuditUnavailable(error) {
|
|
152
|
+
if (error.kind === 'load') {
|
|
153
|
+
return error.detail.kind === 'not-found'
|
|
154
|
+
? 'CLAUDE.md import audit unavailable: audit script not found — reinstall devflow-kit'
|
|
155
|
+
: 'CLAUDE.md import audit unavailable: audit script failed to load — reinstall devflow-kit';
|
|
156
|
+
}
|
|
157
|
+
return 'CLAUDE.md import audit unavailable: the audit failed';
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Write the stamp text to `<devflowDir>/.claude-md-audit` (AC-437, D-AUDIT-STAMP).
|
|
161
|
+
*
|
|
162
|
+
* Refuses, writing nothing: a machine root that does not exist; a stamp path that is
|
|
163
|
+
* a symbolic link, a directory, a FIFO or any other non-regular file (an `lstat`
|
|
164
|
+
* check made here, because the atomic writer renames over the target without
|
|
165
|
+
* looking at what it is). Otherwise the text lands in a sibling temp file renamed
|
|
166
|
+
* into place. Never throws.
|
|
167
|
+
*/
|
|
168
|
+
export async function writeClaudeMdAuditStamp(devflowDir, text) {
|
|
169
|
+
try {
|
|
170
|
+
const root = await fs.stat(devflowDir).catch(() => null);
|
|
171
|
+
if (root === null || !root.isDirectory())
|
|
172
|
+
return { ok: false, error: { kind: 'no-machine-root' } };
|
|
173
|
+
const stampPath = join(devflowDir, CLAUDE_MD_AUDIT_STAMP_FILE);
|
|
174
|
+
const existing = await fs.lstat(stampPath).catch((err) => {
|
|
175
|
+
if (err.code === 'ENOENT')
|
|
176
|
+
return null;
|
|
177
|
+
throw err;
|
|
178
|
+
});
|
|
179
|
+
if (existing !== null && !existing.isFile()) {
|
|
180
|
+
return { ok: false, error: { kind: 'refused', detail: existing.isSymbolicLink() ? 'the stamp path is a symbolic link' : 'the stamp path is not a regular file' } };
|
|
181
|
+
}
|
|
182
|
+
// Owner-only, as the hook writes it: the stamp names the user's project paths.
|
|
183
|
+
await writeFileAtomicExclusive(stampPath, text, STAMP_CREATE_MODE);
|
|
184
|
+
return { ok: true, value: undefined };
|
|
185
|
+
}
|
|
186
|
+
catch (err) {
|
|
187
|
+
return { ok: false, error: { kind: 'write-failed', detail: err instanceof Error ? err.message : String(err) } };
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=claude-md-audit.js.map
|
|
@@ -80,6 +80,16 @@ export function setMachineFeature(rawManifest, feature, enabled, now) {
|
|
|
80
80
|
updatedAt: now,
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Whether a RAW parsed manifest already records `feature` as exactly `enabled`:
|
|
85
|
+
* `features.<feature>` is that boolean. Pure. Stricter than
|
|
86
|
+
* {@link isMachineFeatureOn} on purpose — an absent key, a non-boolean value and
|
|
87
|
+
* a legacy key all read as a state without being recorded as it, so writing the
|
|
88
|
+
* explicit boolean is still a change.
|
|
89
|
+
*/
|
|
90
|
+
function isMachineFeatureRecorded(rawManifest, feature, enabled) {
|
|
91
|
+
return isJsonObject(rawManifest) && isJsonObject(rawManifest.features) && rawManifest.features[feature] === enabled;
|
|
92
|
+
}
|
|
83
93
|
async function readRawManifest(devflowDir) {
|
|
84
94
|
try {
|
|
85
95
|
return JSON.parse(await fs.readFile(path.join(devflowDir, 'manifest.json'), 'utf-8'));
|
|
@@ -101,9 +111,18 @@ export async function readMachineFeature(devflowDir, feature) {
|
|
|
101
111
|
* temp + rename). Refuses with `not-installed` when there is no manifest to
|
|
102
112
|
* write into — a manifest is created by `devflow init`, never by a toggle,
|
|
103
113
|
* because a bare `{features: {...}}` file is not a manifest any reader accepts.
|
|
114
|
+
*
|
|
115
|
+
* D-NOOP-TOGGLE: a toggle to the value the manifest already records writes
|
|
116
|
+
* nothing. The only bytes such a write could change are `updatedAt`, and that
|
|
117
|
+
* field means "the manifest's content last changed", so a no-op must not move
|
|
118
|
+
* it. This is the one write point of `devflow memory|learning|knowledge
|
|
119
|
+
* --enable/--disable`, so the rule holds for all three at once.
|
|
104
120
|
*/
|
|
105
121
|
export async function writeMachineFeature(devflowDir, feature, enabled) {
|
|
106
|
-
const
|
|
122
|
+
const raw = await readRawManifest(devflowDir);
|
|
123
|
+
if (isMachineFeatureRecorded(raw, feature, enabled))
|
|
124
|
+
return { ok: true, value: undefined };
|
|
125
|
+
const next = setMachineFeature(raw, feature, enabled, new Date().toISOString());
|
|
107
126
|
if (next === null)
|
|
108
127
|
return { ok: false, error: 'not-installed' };
|
|
109
128
|
await writeFileAtomicExclusive(path.join(devflowDir, 'manifest.json'), JSON.stringify(next, null, 2) + '\n');
|
package/dist/core/flags.js
CHANGED
|
@@ -423,6 +423,34 @@ export const FLAG_REGISTRY = [
|
|
|
423
423
|
integer: true,
|
|
424
424
|
upstreamDefault: 600000,
|
|
425
425
|
},
|
|
426
|
+
{
|
|
427
|
+
// D-AUTO-COMPACT-WINDOW-OPT-IN: opt-in and unset by default (undefined → manifest null →
|
|
428
|
+
// key never written). devflow writes CLAUDE_CODE_AUTO_COMPACT_WINDOW only when the user sets
|
|
429
|
+
// this flag. Init applies a seeded record without asking, which is why the default is neutral
|
|
430
|
+
// and `recommended` stays false: a smaller window compacts sooner, and a compaction
|
|
431
|
+
// mid-command is exactly what the SessionStart resume directive (D-COMPACT-RESUME-DIRECTIVE)
|
|
432
|
+
// recovers from, so the flag is not recommended until a forced mid-/implement /compact has
|
|
433
|
+
// been seen to resume at the phase after the last finished one (plan §9, a follow-up gate).
|
|
434
|
+
// The percent-based auto-compact override is a different variable that devflow deliberately
|
|
435
|
+
// never writes or names; tests/guards/no-autocompact-pct-override.test.ts keeps it out of src/.
|
|
436
|
+
// The env name was confirmed present in Claude Code 2.1.296, where it takes precedence over
|
|
437
|
+
// the in-app setting, and an in-app message names `auto` or 100k–1M (docs/reference/
|
|
438
|
+
// claude-code-flags-probe.md). No upstreamDefault: the default window and the grammar of the
|
|
439
|
+
// variable's own value were not observed, so the description states only devflow's own
|
|
440
|
+
// accepted range, 100000–1000000.
|
|
441
|
+
id: 'auto-compact-window',
|
|
442
|
+
label: 'Auto-compact window',
|
|
443
|
+
description: 'Context window size in tokens at which Claude Code auto-compacts',
|
|
444
|
+
hint: 'Sets the auto-compact window (100000-1000000); unset keeps the default',
|
|
445
|
+
blurb: 'auto-compact window size',
|
|
446
|
+
kind: 'number',
|
|
447
|
+
target: { type: 'env', key: 'CLAUDE_CODE_AUTO_COMPACT_WINDOW' },
|
|
448
|
+
recommended: false,
|
|
449
|
+
defaultValue: undefined,
|
|
450
|
+
min: 100000, // devflow sanity bound
|
|
451
|
+
max: 1000000, // devflow sanity bound
|
|
452
|
+
integer: true,
|
|
453
|
+
},
|
|
426
454
|
{
|
|
427
455
|
// Writes as { command: value } per Claude Code spellcheck setting shape.
|
|
428
456
|
id: 'spellcheck',
|