devflow-kit 2.4.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +229 -0
- package/README.md +111 -18
- package/dist/agents/git.md +822 -0
- package/dist/cli/commands/agents.js +6 -1
- package/dist/cli/commands/ambient.js +160 -145
- package/dist/cli/commands/attribution-prompts.js +1 -1
- package/dist/cli/commands/capture.js +29 -55
- package/dist/cli/commands/compliance-prompts.js +1 -1
- package/dist/cli/commands/compliance.js +48 -55
- package/dist/cli/commands/context.js +17 -32
- package/dist/cli/commands/debug.js +65 -26
- package/dist/cli/commands/flags.js +3 -3
- package/dist/cli/commands/hud.js +34 -10
- package/dist/cli/commands/init-seed.js +61 -27
- package/dist/cli/commands/init.js +649 -240
- package/dist/cli/commands/install-report.js +200 -0
- package/dist/cli/commands/knowledge/index.js +2 -2
- package/dist/cli/commands/knowledge/toggle.js +35 -37
- package/dist/cli/commands/learning.js +79 -57
- package/dist/cli/commands/legacy-hooks.js +11 -14
- package/dist/cli/commands/memory.js +134 -135
- package/dist/cli/commands/prompt-io.js +4 -4
- package/dist/cli/commands/proxy.js +23 -41
- package/dist/cli/commands/security.js +81 -29
- package/dist/cli/commands/skills.js +71 -7
- package/dist/cli/commands/tracker-prompts.js +145 -0
- package/dist/cli/commands/tracker.js +277 -0
- package/dist/cli/commands/uninstall.js +520 -169
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +58 -14
- package/dist/commands/code-review.md +110 -32
- package/dist/commands/debug.md +55 -11
- package/dist/commands/dynamic-build.md +344 -73
- package/dist/commands/dynamic-plan.md +77 -27
- package/dist/commands/dynamic-profile.md +25 -11
- package/dist/commands/dynamic-tickets.md +76 -15
- package/dist/commands/explore.md +37 -7
- package/dist/commands/implement.md +314 -62
- package/dist/commands/plan.md +146 -32
- package/dist/commands/release.md +64 -17
- package/dist/commands/research.md +34 -8
- package/dist/commands/resolve.md +196 -68
- package/dist/commands/self-review.md +45 -9
- package/dist/core/agent-models.js +55 -12
- package/dist/core/assets.js +58 -2
- package/dist/core/compliance-compose.js +27 -27
- package/dist/core/evidence-policy.js +363 -0
- package/dist/core/feature-config.js +200 -65
- package/dist/core/feature-switch.js +112 -0
- package/dist/core/flags.js +34 -6
- package/dist/core/fs-atomic.js +27 -0
- package/dist/core/hook-log-dirs.js +104 -0
- package/dist/core/learning-tuning-config.js +5 -3
- package/dist/core/ledger-root.js +102 -0
- package/dist/core/manifest.js +38 -10
- package/dist/core/mds-variants.js +798 -0
- package/dist/core/migrations.js +49 -23
- package/dist/core/model-discovery.js +12 -1
- package/dist/core/plugins.js +361 -12
- package/dist/core/project-paths.js +1 -18
- package/dist/core/proxy-log.js +8 -6
- package/dist/core/proxy-state.js +11 -8
- package/dist/core/reference-sweep.js +136 -0
- package/dist/core/same-location.js +25 -0
- package/dist/core/tracker.js +494 -0
- package/dist/hud/components/config-counts.js +15 -4
- package/dist/hud/components/learning-counts.js +14 -0
- package/dist/hud/config.js +2 -1
- package/dist/hud/cost-history.js +2 -4
- package/dist/hud/git.js +52 -7
- package/dist/hud/index.js +7 -9
- package/dist/skills/git/references/decision-markers.md +19 -0
- package/dist/skills/git/references/learn-conventions.md +56 -0
- package/dist/skills/git/references/pr/check-ci-status.md +14 -0
- package/dist/skills/git/references/pr/check-merge-readiness.md +28 -0
- package/dist/skills/git/references/pr/ensure-pr-ready.md +24 -0
- package/dist/skills/git/references/pr/fetch-review-threads.md +22 -0
- package/dist/skills/git/references/pr/post-resolution-summary.md +40 -0
- package/dist/skills/git/references/pr/post-review-summary.md +42 -0
- package/dist/skills/git/references/pr/resolve-review-threads.md +35 -0
- package/dist/skills/git/references/pr/update-pr-evidence.md +14 -0
- package/dist/skills/git/references/pr/validate-branch.md +18 -0
- package/dist/skills/git/references/publication-gate.md +13 -0
- package/dist/skills/git/references/tracker/_mcp.md +153 -0
- package/dist/skills/git/references/tracker/github/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/github/backlink-shipped-issues.md +40 -0
- package/dist/skills/git/references/tracker/github/create-release.md +11 -0
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +16 -0
- package/dist/skills/git/references/tracker/github/ensure-traceable-issue.md +69 -0
- package/dist/skills/git/references/tracker/github/fetch-issue.md +32 -0
- package/dist/skills/git/references/tracker/github/fetch-issues-batch.md +17 -0
- package/dist/skills/git/references/tracker/github/gather-release-evidence.md +19 -0
- package/dist/skills/git/references/tracker/github/manage-debt.md +101 -0
- package/dist/skills/git/references/tracker/github/post-wave-report.md +28 -0
- package/dist/skills/git/references/tracker/github/setup-task.md +26 -0
- package/dist/skills/git/references/tracker/jira/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/jira/backlink-shipped-issues.md +49 -0
- package/dist/skills/git/references/tracker/jira/create-release.md +17 -0
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/jira/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/jira/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/jira/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/jira/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/jira/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/jira/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/jira/setup-task.md +31 -0
- package/dist/skills/git/references/tracker/linear/associate-release.md +18 -0
- package/dist/skills/git/references/tracker/linear/backlink-shipped-issues.md +53 -0
- package/dist/skills/git/references/tracker/linear/create-release.md +17 -0
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +22 -0
- package/dist/skills/git/references/tracker/linear/ensure-traceable-issue.md +53 -0
- package/dist/skills/git/references/tracker/linear/fetch-issue.md +14 -0
- package/dist/skills/git/references/tracker/linear/fetch-issues-batch.md +15 -0
- package/dist/skills/git/references/tracker/linear/gather-release-evidence.md +18 -0
- package/dist/skills/git/references/tracker/linear/manage-debt.md +37 -0
- package/dist/skills/git/references/tracker/linear/post-wave-report.md +33 -0
- package/dist/skills/git/references/tracker/linear/setup-task.md +32 -0
- package/dist/skills/git/references/trust-rule.md +7 -0
- package/dist/targets/claude-code/claude-paths.js +59 -57
- package/dist/targets/claude-code/compliance-install.js +49 -65
- package/dist/targets/claude-code/hooks.js +108 -3
- package/dist/targets/claude-code/installer.js +1187 -32
- package/dist/targets/claude-code/legacy.js +5 -0
- package/dist/targets/claude-code/post-install.js +366 -151
- package/dist/targets/claude-code/tracker-install.js +134 -0
- package/package.json +8 -6
- package/src/assets/agents/code.md +45 -6
- package/src/assets/agents/design.md +2 -1
- package/src/assets/agents/git.mds +825 -0
- package/src/assets/agents/knowledge.md +3 -3
- package/src/assets/agents/learning.md +11 -0
- package/src/assets/agents/review.md +3 -1
- package/src/assets/agents/synthesize.md +1 -1
- package/src/assets/agents/test.md +16 -5
- package/src/assets/agents/tracker.md +474 -0
- package/src/assets/agents/validate.md +7 -5
- package/src/assets/commands/_partials/_compliance.mds +19 -1
- package/src/assets/commands/_partials/_decisions.mds +15 -3
- package/src/assets/commands/_partials/_docs_root.mds +35 -0
- package/src/assets/commands/_partials/_engine.mds +13 -11
- package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
- package/src/assets/commands/_partials/_factory.mds +1 -1
- package/src/assets/commands/_partials/_knowledge.mds +27 -9
- package/src/assets/commands/_partials/_plan_contract.mds +22 -7
- package/src/assets/commands/_partials/_preamble.mds +2 -2
- package/src/assets/commands/_partials/_publication.mds +8 -2
- package/src/assets/commands/_partials/_settings.mds +28 -0
- package/src/assets/commands/_partials/_ticket_template.mds +3 -2
- package/src/assets/commands/_partials/_tracker.mds +18 -0
- package/src/assets/commands/_partials/_wave.mds +16 -10
- package/src/assets/commands/bug-analysis.mds +31 -19
- package/src/assets/commands/code-review.mds +67 -41
- package/src/assets/commands/debug.mds +13 -7
- package/src/assets/commands/dynamic-build.mds +274 -66
- package/src/assets/commands/dynamic-plan.mds +50 -23
- package/src/assets/commands/dynamic-profile.mds +24 -11
- package/src/assets/commands/dynamic-tickets.mds +63 -16
- package/src/assets/commands/explore.mds +4 -5
- package/src/assets/commands/implement.mds +234 -67
- package/src/assets/commands/plan.mds +91 -33
- package/src/assets/commands/release.md +64 -17
- package/src/assets/commands/research.mds +11 -9
- package/src/assets/commands/resolve.mds +150 -78
- package/src/assets/commands/self-review.mds +24 -25
- package/src/assets/mds/git/_pr.mds +331 -0
- package/src/assets/mds/git/_references.mds +135 -0
- package/src/assets/mds/tracker/_common.mds +156 -0
- package/src/assets/mds/tracker/_github.mds +472 -0
- package/src/assets/mds/tracker/_jira.mds +407 -0
- package/src/assets/mds/tracker/_linear.mds +449 -0
- package/src/assets/mds/tracker/_mcp.mds +305 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
- package/src/assets/scripts/hooks/background-memory-update +40 -19
- package/src/assets/scripts/hooks/capture-prompt +18 -8
- package/src/assets/scripts/hooks/capture-question +18 -8
- package/src/assets/scripts/hooks/capture-turn +27 -13
- package/src/assets/scripts/hooks/debug-trace +11 -6
- package/src/assets/scripts/hooks/ensure-devflow-init +33 -6
- package/src/assets/scripts/hooks/ensure-proxy +9 -8
- package/src/assets/scripts/hooks/ensure-root-gitignore +236 -60
- package/src/assets/scripts/hooks/git-marker +48 -0
- package/src/assets/scripts/hooks/hook-log-init +3 -1
- package/src/assets/scripts/hooks/json-helper.cjs +228 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -20
- package/src/assets/scripts/hooks/log-paths +80 -0
- package/src/assets/scripts/hooks/memory-worker +22 -13
- package/src/assets/scripts/hooks/pre-compact-memory +44 -15
- package/src/assets/scripts/hooks/preamble +1 -4
- package/src/assets/scripts/hooks/queue-append +146 -28
- package/src/assets/scripts/hooks/resolve-project-root +101 -7
- package/src/assets/scripts/hooks/session-start-context +534 -20
- package/src/assets/scripts/hooks/session-start-memory +38 -15
- package/src/assets/scripts/lib/project-config.cjs +633 -0
- package/src/assets/scripts/pr-evidence.cjs +1961 -0
- package/src/assets/scripts/redact-secrets.cjs +490 -62
- package/src/assets/scripts/release-trace.cjs +1143 -0
- package/src/assets/scripts/resolve-evidence-policy.cjs +1145 -0
- package/src/assets/scripts/resolve-settings.cjs +1054 -0
- package/src/assets/scripts/verify-evidence.cjs +1822 -0
- package/src/assets/skills/compliance/SKILL.md +4 -2
- package/src/assets/skills/docs-framework/SKILL.md +11 -10
- package/src/assets/skills/docs-framework/references/patterns.md +10 -17
- package/src/assets/skills/gap-analysis/SKILL.md +2 -2
- package/src/assets/skills/git/SKILL.md +8 -78
- package/src/assets/skills/git/references/github-api.md +179 -141
- package/src/assets/skills/git/references/patterns.md +11 -6
- package/src/assets/skills/review-methodology/SKILL.md +1 -1
- package/src/assets/skills/review-methodology/references/patterns.md +6 -61
- package/src/assets/skills/review-methodology/references/violations.md +14 -22
- package/src/assets/skills/worktree-support/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +29 -0
- package/src/targets/claude-code/templates/managed-settings.json +25 -9
- package/src/assets/agents/git.md +0 -938
|
@@ -6,15 +6,20 @@
|
|
|
6
6
|
* Applies ADR-001: compliance is manifest-group (like proxy), not config.json-gated.
|
|
7
7
|
* Avoids PF-009: per-artifact failures are warn-not-throw.
|
|
8
8
|
* Avoids PF-015: enable/disable each converge BOTH artifacts unconditionally.
|
|
9
|
+
* The evidence-policy lines (--status, and the --enable/--set suggestion) come
|
|
10
|
+
* from src/core/evidence-policy.ts, the seam onto the package's own resolvers;
|
|
11
|
+
* the CLI prints the keys to add to .devflow/project.json and never writes it
|
|
12
|
+
* (applies ADR-024).
|
|
9
13
|
*/
|
|
10
14
|
import { Command } from 'commander';
|
|
11
15
|
import { promises as fs } from 'fs';
|
|
12
16
|
import * as path from 'path';
|
|
13
17
|
import * as p from '@clack/prompts';
|
|
14
18
|
import color from 'picocolors';
|
|
15
|
-
import {
|
|
19
|
+
import { COMPLIANCE_FRAMEWORKS, normalizeFrameworks, parseFrameworkList, } from '../../core/compliance.js';
|
|
16
20
|
import { frameworkChoices, FRAMEWORK_SELECT_MESSAGE } from './compliance-prompts.js';
|
|
17
21
|
import { COMPLIANCE_SKILL_TOKENS } from '../../core/compliance-compose.js';
|
|
22
|
+
import { evidencePolicyStatusLine, evidencePolicySuggestion, formatEvidencePolicyUnavailable, loadEvidencePolicyModule, loadSettingsModule, repoComplianceStatusLines, } from '../../core/evidence-policy.js';
|
|
18
23
|
import { readManifest, writeManifest } from '../../core/manifest.js';
|
|
19
24
|
import { convergeFromManifest } from '../../targets/claude-code/compliance-install.js';
|
|
20
25
|
import { validateRuleShadow, validateSkillShadow } from '../../targets/claude-code/installer.js';
|
|
@@ -61,7 +66,7 @@ export function resolveComplianceCliAction(current, action, setFrameworks) {
|
|
|
61
66
|
messages: [
|
|
62
67
|
{
|
|
63
68
|
level: 'success',
|
|
64
|
-
text: 'Compliance disabled —
|
|
69
|
+
text: 'Compliance disabled — rule removed, frameworks remembered for re-enable',
|
|
65
70
|
},
|
|
66
71
|
],
|
|
67
72
|
};
|
|
@@ -89,23 +94,17 @@ export function resolveComplianceCliAction(current, action, setFrameworks) {
|
|
|
89
94
|
}
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
|
-
// ──
|
|
97
|
+
// ── Manifest classification ────────────────────────────────────────────────────
|
|
93
98
|
/**
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
99
|
+
* The manifest framework IDs the registry does not know — a hand-edited or
|
|
100
|
+
* newer-devflow manifest. Every install drops them (normalizeFrameworks), so
|
|
101
|
+
* `--status` names them with the one remedy that removes them: `--set`.
|
|
97
102
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* logic in isolation.
|
|
103
|
+
* Installed reference files are no drift signal: every install carries all six
|
|
104
|
+
* (D-COMPLIANCE-INSTALL-ALWAYS), whatever the manifest selects.
|
|
101
105
|
*/
|
|
102
|
-
export function
|
|
103
|
-
|
|
104
|
-
const missing = manifestFrameworks.filter(id => !installedSet.has(id));
|
|
105
|
-
return {
|
|
106
|
-
validMissing: missing.filter(id => registryIds.has(id)),
|
|
107
|
-
invalidIds: missing.filter(id => !registryIds.has(id)),
|
|
108
|
-
};
|
|
106
|
+
export function unknownFrameworkIds(manifestFrameworks, registryIds) {
|
|
107
|
+
return manifestFrameworks.filter(id => !registryIds.has(id));
|
|
109
108
|
}
|
|
110
109
|
// ── Status helpers ─────────────────────────────────────────────────────────────
|
|
111
110
|
/** Returns true if the compliance skill dir exists at the install target. */
|
|
@@ -118,23 +117,6 @@ async function skillInstalled(claudeDir) {
|
|
|
118
117
|
return false;
|
|
119
118
|
}
|
|
120
119
|
}
|
|
121
|
-
/** Returns the set of installed framework reference IDs from the skill dir. */
|
|
122
|
-
async function installedRefIds(claudeDir) {
|
|
123
|
-
const refDir = path.join(claudeDir, 'skills', 'devflow:compliance', 'references');
|
|
124
|
-
try {
|
|
125
|
-
const entries = await fs.readdir(refDir);
|
|
126
|
-
return entries
|
|
127
|
-
.filter(e => e.endsWith('.md') && !ALWAYS_PRESENT_REFS.includes(e))
|
|
128
|
-
.map(e => path.basename(e, '.md'))
|
|
129
|
-
// S58: sanitize — keep only entries whose basename matches the expected id
|
|
130
|
-
// shape (lowercase letters, digits, hyphens). Strips terminal-escape sequences
|
|
131
|
-
// or path segments that could be injected via a crafted filename.
|
|
132
|
-
.filter(id => /^[a-z0-9-]+$/.test(id));
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
return [];
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
120
|
/** Returns true if the compliance rule file exists at the install target. */
|
|
139
121
|
async function ruleInstalled(claudeDir) {
|
|
140
122
|
try {
|
|
@@ -176,8 +158,8 @@ async function skillShadowState(devflowDir) {
|
|
|
176
158
|
export const complianceCommand = new Command('compliance')
|
|
177
159
|
.description('Enable, disable, or configure the compliance feature')
|
|
178
160
|
.option('--enable', 'Enable compliance (restores previously selected frameworks)')
|
|
179
|
-
.option('--disable', 'Disable compliance (
|
|
180
|
-
.option('--status', 'Show compliance state: manifest, installed artifacts, and
|
|
161
|
+
.option('--disable', 'Disable compliance (rule removed; frameworks remembered for re-enable)')
|
|
162
|
+
.option('--status', 'Show compliance state: manifest, installed artifacts, shadow presence, and the evidence policy for the current repository')
|
|
181
163
|
.option('--set <list>', 'Set active frameworks (comma-separated IDs); enables compliance. Use --set "" for zero frameworks (generic controls only)')
|
|
182
164
|
.action(async (options) => {
|
|
183
165
|
const claudeDir = getClaudeDirectory();
|
|
@@ -217,24 +199,22 @@ export const complianceCommand = new Command('compliance')
|
|
|
217
199
|
? current.frameworks.join(', ')
|
|
218
200
|
: color.dim('none declared');
|
|
219
201
|
const rulesEnabled = manifest.features.rules;
|
|
220
|
-
const [skillOk,
|
|
202
|
+
const [skillOk, ruleOk, isRuleShadowed, skillShadow] = await Promise.all([
|
|
221
203
|
skillInstalled(claudeDir),
|
|
222
|
-
installedRefIds(claudeDir),
|
|
223
204
|
ruleInstalled(claudeDir),
|
|
224
205
|
ruleShadowed(devflowDir),
|
|
225
206
|
skillShadowState(devflowDir),
|
|
226
207
|
]);
|
|
227
|
-
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
const
|
|
233
|
-
const driftInstalled = refIds.filter(id => !manifestSet.has(id));
|
|
234
|
-
const { validMissing, invalidIds } = classifyDriftMissing(current.frameworks, refIds, registrySet);
|
|
208
|
+
const invalidIds = unknownFrameworkIds(current.frameworks, new Set(COMPLIANCE_FRAMEWORKS.map(fw => fw.id)));
|
|
209
|
+
// The repository's own declaration (.devflow/project.json) and, while the
|
|
210
|
+
// retired policy file is in the working tree, the hint to migrate it. Both come from
|
|
211
|
+
// the local settings resolver — one git call, no network — and add nothing
|
|
212
|
+
// when the repository declares nothing (the block is then unchanged).
|
|
213
|
+
const repoLines = repoComplianceStatusLines(loadSettingsModule(), { dir: process.cwd() });
|
|
235
214
|
const lines = [
|
|
236
215
|
`State: ${enabledLabel}`,
|
|
237
216
|
`Frameworks: ${fwLabel}`,
|
|
217
|
+
...repoLines,
|
|
238
218
|
'',
|
|
239
219
|
`Skill: ${skillOk ? color.green('installed') : color.dim('not installed')}` +
|
|
240
220
|
(skillShadow === 'composition-skipped'
|
|
@@ -247,18 +227,14 @@ export const complianceCommand = new Command('compliance')
|
|
|
247
227
|
? color.yellow(' (withheld — rules disabled)')
|
|
248
228
|
: '') +
|
|
249
229
|
(isRuleShadowed ? color.green(' [shadowed]') : ''),
|
|
230
|
+
'',
|
|
231
|
+
// The repository in cwd, resolved by the package's own resolver with the
|
|
232
|
+
// compliance state already read above (D-POLICY-CJS-SEAM). Bounded: at most
|
|
233
|
+
// three `gh` calls, each with a timeout, so offline degrades to a flagged line.
|
|
234
|
+
evidencePolicyStatusLine(loadEvidencePolicyModule(), { dir: process.cwd(), compliance: current }),
|
|
250
235
|
];
|
|
251
|
-
if (
|
|
236
|
+
if (invalidIds.length > 0) {
|
|
252
237
|
lines.push('');
|
|
253
|
-
if (driftInstalled.length > 0 || validMissing.length > 0) {
|
|
254
|
-
lines.push(color.yellow('Artifact drift detected (run devflow compliance --enable to reconcile):'));
|
|
255
|
-
if (driftInstalled.length > 0) {
|
|
256
|
-
lines.push(` Installed not in manifest: ${driftInstalled.join(', ')}`);
|
|
257
|
-
}
|
|
258
|
-
if (validMissing.length > 0) {
|
|
259
|
-
lines.push(` In manifest but not installed: ${validMissing.join(', ')}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
238
|
for (const id of invalidIds) {
|
|
263
239
|
lines.push(color.red(` unknown framework id in manifest (ignored): ${id} — remove with --set`));
|
|
264
240
|
}
|
|
@@ -333,5 +309,22 @@ export const complianceCommand = new Command('compliance')
|
|
|
333
309
|
p.log.info(color.dim('Note: compliance rule withheld (rules disabled) — ' +
|
|
334
310
|
'run `devflow rules --enable` to install the stamped rule'));
|
|
335
311
|
}
|
|
312
|
+
// Suggest the team file compliance now implies. Printed, never written:
|
|
313
|
+
// .devflow/project.json is team-owned (D-POLICY-NO-WRITE, applies ADR-024).
|
|
314
|
+
if (resolved.nextState.enabled) {
|
|
315
|
+
const policyModule = loadEvidencePolicyModule();
|
|
316
|
+
const settingsModule = loadSettingsModule();
|
|
317
|
+
if (!policyModule.ok) {
|
|
318
|
+
p.log.warn(formatEvidencePolicyUnavailable(policyModule.error));
|
|
319
|
+
}
|
|
320
|
+
else if (!settingsModule.ok) {
|
|
321
|
+
p.log.warn(formatEvidencePolicyUnavailable(settingsModule.error));
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
const suggestion = evidencePolicySuggestion(resolved.nextState, policyModule.value, settingsModule.value);
|
|
325
|
+
if (suggestion !== null)
|
|
326
|
+
p.note(suggestion, 'Evidence policy');
|
|
327
|
+
}
|
|
328
|
+
}
|
|
336
329
|
});
|
|
337
330
|
//# sourceMappingURL=compliance.js.map
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { devflowHookOwner, hasHook, removeHooks, runHookCommand, } from '../../targets/claude-code/hooks.js';
|
|
2
2
|
// ─── Context hook utilities ────────────────────────────────────────────────
|
|
3
3
|
//
|
|
4
4
|
// The session-start-context hook is always-on (registered unconditionally by
|
|
5
5
|
// init, removed by uninstall). It has internal sentinel awareness per feature.
|
|
6
6
|
const CONTEXT_HOOK_MARKER = 'session-start-context';
|
|
7
|
+
/**
|
|
8
|
+
* D-EXACT-HOOK-OWNER: the context hook is devflow's only when its command ends in
|
|
9
|
+
* `/scripts/hooks/run-hook session-start-context` (hooks.ts), under any directory.
|
|
10
|
+
* It has been registered through run-hook since it first shipped, so there is no
|
|
11
|
+
* legacy form to recognise.
|
|
12
|
+
*/
|
|
13
|
+
const isContextHook = devflowHookOwner([CONTEXT_HOOK_MARKER]);
|
|
7
14
|
/**
|
|
8
15
|
* Add the session-start-context hook to SessionStart in settings JSON.
|
|
9
16
|
* Idempotent — returns unchanged JSON if hook already present.
|
|
@@ -13,46 +20,24 @@ export function addContextHook(settingsJson, devflowDir) {
|
|
|
13
20
|
return settingsJson;
|
|
14
21
|
}
|
|
15
22
|
const settings = JSON.parse(settingsJson);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
hooks: [
|
|
22
|
-
{
|
|
23
|
-
type: 'command',
|
|
24
|
-
command: hookCommand,
|
|
25
|
-
timeout: 10,
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
};
|
|
29
|
-
if (!settings.hooks.SessionStart) {
|
|
30
|
-
settings.hooks.SessionStart = [];
|
|
31
|
-
}
|
|
32
|
-
settings.hooks.SessionStart.push(newEntry);
|
|
23
|
+
settings.hooks ??= {};
|
|
24
|
+
settings.hooks.SessionStart ??= [];
|
|
25
|
+
settings.hooks.SessionStart.push({
|
|
26
|
+
hooks: [{ type: 'command', command: runHookCommand(devflowDir, CONTEXT_HOOK_MARKER), timeout: 10 }],
|
|
27
|
+
});
|
|
33
28
|
return JSON.stringify(settings, null, 2) + '\n';
|
|
34
29
|
}
|
|
35
30
|
/**
|
|
36
31
|
* Remove the session-start-context hook from settings JSON.
|
|
37
32
|
* Idempotent — returns unchanged JSON if hook not present.
|
|
38
|
-
*
|
|
33
|
+
* Removes the single hook, so the other hooks of its matcher group and every
|
|
34
|
+
* other SessionStart group stay in place (D-EXACT-HOOK-OWNER).
|
|
39
35
|
*/
|
|
40
36
|
export function removeContextHook(settingsJson) {
|
|
41
37
|
const settings = JSON.parse(settingsJson);
|
|
42
|
-
if (!settings
|
|
38
|
+
if (!removeHooks(settings, 'SessionStart', isContextHook)) {
|
|
43
39
|
return settingsJson;
|
|
44
40
|
}
|
|
45
|
-
const before = settings.hooks.SessionStart.length;
|
|
46
|
-
settings.hooks.SessionStart = settings.hooks.SessionStart.filter((matcher) => !matcher.hooks.some((h) => h.command.includes(CONTEXT_HOOK_MARKER)));
|
|
47
|
-
if (settings.hooks.SessionStart.length === before) {
|
|
48
|
-
return settingsJson;
|
|
49
|
-
}
|
|
50
|
-
if (settings.hooks.SessionStart.length === 0) {
|
|
51
|
-
delete settings.hooks.SessionStart;
|
|
52
|
-
}
|
|
53
|
-
if (Object.keys(settings.hooks).length === 0) {
|
|
54
|
-
delete settings.hooks;
|
|
55
|
-
}
|
|
56
41
|
return JSON.stringify(settings, null, 2) + '\n';
|
|
57
42
|
}
|
|
58
43
|
/**
|
|
@@ -61,6 +46,6 @@ export function removeContextHook(settingsJson) {
|
|
|
61
46
|
*/
|
|
62
47
|
export function hasContextHook(input) {
|
|
63
48
|
const settings = typeof input === 'string' ? JSON.parse(input) : input;
|
|
64
|
-
return settings
|
|
49
|
+
return hasHook(settings, 'SessionStart', isContextHook);
|
|
65
50
|
}
|
|
66
51
|
//# sourceMappingURL=context.js.map
|
|
@@ -4,17 +4,47 @@ import * as path from 'path';
|
|
|
4
4
|
import * as p from '@clack/prompts';
|
|
5
5
|
import color from 'picocolors';
|
|
6
6
|
import { getClaudeDirectory, getHomeDirectory } from '../../targets/claude-code/claude-paths.js';
|
|
7
|
-
|
|
7
|
+
import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
|
|
8
|
+
function isPlainObject(value) {
|
|
9
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The settings object and its `env` (undefined when absent), or why not.
|
|
13
|
+
*
|
|
14
|
+
* D-DEBUG-ENV-OBJECT: Claude Code reads `env` as an object of variables. A
|
|
15
|
+
* present `env` that is anything else — an array, a string, `null` — is
|
|
16
|
+
* rejected, never repaired and never written through: a key set on an array is
|
|
17
|
+
* dropped by JSON.stringify, so the command would report success and write
|
|
18
|
+
* nothing, and replacing the value would discard what the user wrote.
|
|
19
|
+
*/
|
|
20
|
+
function parseSettingsEnv(settingsJson) {
|
|
21
|
+
let settings;
|
|
22
|
+
try {
|
|
23
|
+
settings = JSON.parse(settingsJson);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return { ok: false, error: { kind: 'malformed' } };
|
|
27
|
+
}
|
|
28
|
+
if (!isPlainObject(settings))
|
|
29
|
+
return { ok: false, error: { kind: 'malformed' } };
|
|
30
|
+
if (!Object.prototype.hasOwnProperty.call(settings, 'env'))
|
|
31
|
+
return { ok: true, settings, env: undefined };
|
|
32
|
+
const env = settings.env;
|
|
33
|
+
if (!isPlainObject(env))
|
|
34
|
+
return { ok: false, error: { kind: 'env-not-object' } };
|
|
35
|
+
return { ok: true, settings, env };
|
|
36
|
+
}
|
|
8
37
|
/**
|
|
9
38
|
* Apply DEVFLOW_HOOK_DEBUG=1 to a settings JSON string.
|
|
10
39
|
* Returns a new serialized settings string. Does not mutate.
|
|
11
40
|
* Follows the applyFlags pattern from flags.ts.
|
|
12
41
|
*/
|
|
13
42
|
export function applyDebugTrace(settingsJson) {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
43
|
+
const parsed = parseSettingsEnv(settingsJson);
|
|
44
|
+
if (!parsed.ok)
|
|
45
|
+
return parsed;
|
|
46
|
+
const next = { ...parsed.settings, env: { ...parsed.env, DEVFLOW_HOOK_DEBUG: '1' } };
|
|
47
|
+
return { ok: true, value: JSON.stringify(next, null, 2) + '\n' };
|
|
18
48
|
}
|
|
19
49
|
/**
|
|
20
50
|
* Remove DEVFLOW_HOOK_DEBUG from a settings JSON string.
|
|
@@ -23,15 +53,28 @@ export function applyDebugTrace(settingsJson) {
|
|
|
23
53
|
* Follows the stripFlags pattern from flags.ts.
|
|
24
54
|
*/
|
|
25
55
|
export function stripDebugTrace(settingsJson) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
56
|
+
const parsed = parseSettingsEnv(settingsJson);
|
|
57
|
+
if (!parsed.ok)
|
|
58
|
+
return parsed;
|
|
59
|
+
if (parsed.env === undefined)
|
|
60
|
+
return { ok: true, value: JSON.stringify(parsed.settings, null, 2) + '\n' };
|
|
61
|
+
const without = (obj, key) => Object.fromEntries(Object.entries(obj).filter(([k]) => k !== key));
|
|
62
|
+
const env = without(parsed.env, 'DEVFLOW_HOOK_DEBUG');
|
|
63
|
+
const next = Object.keys(env).length === 0 ? without(parsed.settings, 'env') : { ...parsed.settings, env };
|
|
64
|
+
return { ok: true, value: JSON.stringify(next, null, 2) + '\n' };
|
|
65
|
+
}
|
|
66
|
+
/** The message a rejected settings file gets. Pure. */
|
|
67
|
+
export function describeDebugSettingsError(error) {
|
|
68
|
+
switch (error.kind) {
|
|
69
|
+
case 'malformed':
|
|
70
|
+
return 'settings.json is malformed — fix it before modifying env vars';
|
|
71
|
+
case 'env-not-object':
|
|
72
|
+
return 'settings.json has an "env" that is not an object — fix it before modifying env vars';
|
|
73
|
+
default: {
|
|
74
|
+
const exhaustive = error;
|
|
75
|
+
return exhaustive;
|
|
32
76
|
}
|
|
33
77
|
}
|
|
34
|
-
return JSON.stringify(settings, null, 2) + '\n';
|
|
35
78
|
}
|
|
36
79
|
/**
|
|
37
80
|
* Read the debug tracing state from a settings JSON string.
|
|
@@ -90,29 +133,25 @@ export const debugCommand = new Command('debug')
|
|
|
90
133
|
settingsJson = '{}';
|
|
91
134
|
}
|
|
92
135
|
if (options.enable) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
updated
|
|
96
|
-
|
|
97
|
-
catch {
|
|
98
|
-
p.log.error('settings.json is malformed — fix it before modifying env vars');
|
|
136
|
+
const updated = applyDebugTrace(settingsJson);
|
|
137
|
+
if (!updated.ok) {
|
|
138
|
+
p.log.error(describeDebugSettingsError(updated.error));
|
|
139
|
+
process.exitCode = 1;
|
|
99
140
|
return;
|
|
100
141
|
}
|
|
101
|
-
await
|
|
142
|
+
await writeSettingsFileAtomic(settingsPath, updated.value);
|
|
102
143
|
p.log.success('Hook debug tracing enabled');
|
|
103
144
|
p.log.info(color.dim('Remember to disable after debugging: devflow debug --disable'));
|
|
104
145
|
return;
|
|
105
146
|
}
|
|
106
147
|
if (options.disable) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
updated
|
|
110
|
-
|
|
111
|
-
catch {
|
|
112
|
-
p.log.error('settings.json is malformed — fix it before modifying env vars');
|
|
148
|
+
const updated = stripDebugTrace(settingsJson);
|
|
149
|
+
if (!updated.ok) {
|
|
150
|
+
p.log.error(describeDebugSettingsError(updated.error));
|
|
151
|
+
process.exitCode = 1;
|
|
113
152
|
return;
|
|
114
153
|
}
|
|
115
|
-
await
|
|
154
|
+
await writeSettingsFileAtomic(settingsPath, updated.value);
|
|
116
155
|
p.log.success('Hook debug tracing disabled');
|
|
117
156
|
return;
|
|
118
157
|
}
|
|
@@ -23,7 +23,7 @@ import color from 'picocolors';
|
|
|
23
23
|
import { getClaudeDirectory, getDevFlowDirectory, } from '../../targets/claude-code/claude-paths.js';
|
|
24
24
|
import { FLAG_REGISTRY, findFlag, convergeFlagsIntoSettings, parseFlagValueInput, formatFlagValue, effectiveDisplay, neutralValueOf, describeFlagKind, expectedInputFor, } from '../../core/flags.js';
|
|
25
25
|
import { readManifest, writeManifest } from '../../core/manifest.js';
|
|
26
|
-
import {
|
|
26
|
+
import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
|
|
27
27
|
import { sanitizeCell } from '../tui/cells.js';
|
|
28
28
|
// Static imports for pure view-state helpers — no TTY machinery (applies PF-017).
|
|
29
29
|
// runFlagsTui stays lazily imported in handleBare to keep TTY module out of
|
|
@@ -98,7 +98,7 @@ manifest, opts = { viewModeExplicit: false }) {
|
|
|
98
98
|
// Settings write — independent error path (avoids PF-015 fan-out).
|
|
99
99
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
100
100
|
try {
|
|
101
|
-
await
|
|
101
|
+
await writeSettingsFileAtomic(settingsPath, updatedSettings);
|
|
102
102
|
}
|
|
103
103
|
catch (err) {
|
|
104
104
|
p.log.error(`Failed to write settings.json: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -456,7 +456,7 @@ async function handleBare(claudeDir, devflowDir) {
|
|
|
456
456
|
// The read captured before runFlagsTui is a stale snapshot by the time the
|
|
457
457
|
// user saves — any concurrent writer (proxy enable, devflow agents, Claude
|
|
458
458
|
// Code /config) that ran during the session would be silently overwritten by
|
|
459
|
-
// the atomic rename in
|
|
459
|
+
// the atomic rename in writeSettingsFileAtomic. Re-reading rebases the flag
|
|
460
460
|
// write onto current content and ensures convergeFlagsIntoSettings sees the
|
|
461
461
|
// fresh viewMode (applies PF-022 — file state, not config state, is reality).
|
|
462
462
|
const freshSettings = await readSettingsSafe(path.join(claudeDir, 'settings.json'));
|
package/dist/cli/commands/hud.js
CHANGED
|
@@ -5,12 +5,13 @@ import * as p from '@clack/prompts';
|
|
|
5
5
|
import color from 'picocolors';
|
|
6
6
|
import { getClaudeDirectory, getDevFlowDirectory } from '../../targets/claude-code/claude-paths.js';
|
|
7
7
|
import { syncManifestFeature } from '../../core/manifest.js';
|
|
8
|
-
import {
|
|
8
|
+
import { writeSettingsFileAtomic } from '../../core/fs-atomic.js';
|
|
9
9
|
import { HUD_COMPONENTS, loadConfig, saveConfig, } from '../../hud/config.js';
|
|
10
10
|
/**
|
|
11
11
|
* Add the HUD statusLine to settings JSON.
|
|
12
12
|
* Idempotent — returns unchanged JSON if HUD already set.
|
|
13
|
-
* Upgrades legacy statusline.sh to hud.sh automatically
|
|
13
|
+
* Upgrades devflow's legacy statusline.sh to hud.sh automatically; a statusLine
|
|
14
|
+
* that is not devflow's is returned unchanged (D-HUD-EXACT-OWNER).
|
|
14
15
|
*/
|
|
15
16
|
export function addHudStatusLine(settingsJson, devflowDir) {
|
|
16
17
|
const settings = JSON.parse(settingsJson);
|
|
@@ -54,16 +55,39 @@ export function hasHudStatusLine(settingsJson) {
|
|
|
54
55
|
return false;
|
|
55
56
|
return isDevFlowStatusLine(settings.statusLine);
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* The statusLine command endings devflow has ever written: the HUD, and the
|
|
60
|
+
* pre-HUD `statusline.sh` it replaced.
|
|
61
|
+
*/
|
|
62
|
+
const DEVFLOW_STATUSLINE_SUFFIXES = [
|
|
63
|
+
'/.devflow/scripts/hud.sh',
|
|
64
|
+
'/.devflow/scripts/statusline.sh',
|
|
65
|
+
];
|
|
57
66
|
/**
|
|
58
67
|
* Check if an existing statusLine belongs to Devflow (HUD or legacy statusline).
|
|
59
|
-
*
|
|
68
|
+
*
|
|
69
|
+
* D-HUD-EXACT-OWNER: a statusLine is devflow's only when its command ends in
|
|
70
|
+
* `/.devflow/scripts/hud.sh` or the legacy `/.devflow/scripts/statusline.sh`, under
|
|
71
|
+
* any parent directory — so installs from the custom-directory and local-scope era
|
|
72
|
+
* are still recognised. A bare `statusline.sh` (the Claude Code docs' own example,
|
|
73
|
+
* `~/.claude/statusline.sh`) or a path that merely contains a `devflow` segment is
|
|
74
|
+
* the user's (applies ADR-024: remove or replace only what devflow provably wrote).
|
|
75
|
+
* Backslashes are read as slashes so a Windows install is matched the same way. A
|
|
76
|
+
* hand-edited command that is not a string is the user's, as in `endsWithAny`.
|
|
77
|
+
*
|
|
78
|
+
* Every caller converges through this one predicate: `addHudStatusLine` (init's
|
|
79
|
+
* settings pass with the HUD on, `init --hud-only`, `hud --enable`),
|
|
80
|
+
* `removeHudStatusLine` (init's settings pass with `--no-hud`, `hud --disable`,
|
|
81
|
+
* uninstall's `runCleanupPhase`), and `hasHudStatusLine` / `hasNonDevFlowStatusLine`
|
|
82
|
+
* (`hud --enable`, `hud --status`).
|
|
60
83
|
*/
|
|
61
84
|
function isDevFlowStatusLine(statusLine) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
85
|
+
// Parsed from a hand-editable settings.json, so the declared type is not a guarantee.
|
|
86
|
+
const raw = statusLine.command;
|
|
87
|
+
if (typeof raw !== 'string')
|
|
88
|
+
return false;
|
|
89
|
+
const cmd = raw.trim().replace(/\\/g, '/');
|
|
90
|
+
return DEVFLOW_STATUSLINE_SUFFIXES.some((suffix) => cmd.endsWith(suffix));
|
|
67
91
|
}
|
|
68
92
|
/**
|
|
69
93
|
* Check if an existing statusLine belongs to a non-Devflow tool.
|
|
@@ -171,7 +195,7 @@ export function createHudCommand() {
|
|
|
171
195
|
}
|
|
172
196
|
}
|
|
173
197
|
const updated = addHudStatusLine(settingsContent, devflowDir);
|
|
174
|
-
await
|
|
198
|
+
await writeSettingsFileAtomic(settingsPath, updated);
|
|
175
199
|
}
|
|
176
200
|
// Always update config and sync manifest — removing the already-enabled
|
|
177
201
|
// early-return makes --enable self-healing symmetric with --disable.
|
|
@@ -209,7 +233,7 @@ export function createHudCommand() {
|
|
|
209
233
|
const settingsContent = await fs.readFile(settingsPath, 'utf-8');
|
|
210
234
|
const updated = removeHudStatusLine(settingsContent);
|
|
211
235
|
if (updated !== settingsContent) {
|
|
212
|
-
await
|
|
236
|
+
await writeSettingsFileAtomic(settingsPath, updated);
|
|
213
237
|
statusLineRemoved = true;
|
|
214
238
|
}
|
|
215
239
|
}
|