devflow-kit 3.8.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 +231 -0
- package/README.md +6 -6
- package/dist/agents/code.md +10 -10
- package/dist/agents/fix.md +7 -7
- package/dist/agents/git.md +2 -2
- package/dist/agents/harden.md +7 -6
- package/dist/agents/refactor.md +6 -6
- package/dist/agents/skim.md +1 -1
- package/dist/agents/triage.md +7 -0
- 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 +79 -23
- package/dist/cli/commands/gate-level-prompts.js +39 -23
- package/dist/cli/commands/gates.js +6 -7
- package/dist/cli/commands/init.js +150 -145
- package/dist/cli/commands/install-report.js +34 -0
- package/dist/cli/commands/legacy-sweep.js +64 -0
- package/dist/cli/commands/memory.js +59 -12
- 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 +354 -60
- package/dist/commands/bug-analysis.md +71 -122
- package/dist/commands/code-review.md +11 -11
- package/dist/commands/debug.md +2 -2
- package/dist/commands/dynamic-build.md +25 -25
- package/dist/commands/dynamic-plan.md +2 -2
- package/dist/commands/dynamic-profile.md +2 -2
- package/dist/commands/dynamic-tickets.md +2 -2
- package/dist/commands/explore.md +2 -2
- package/dist/commands/implement.md +48 -34
- package/dist/commands/plan.md +2 -2
- package/dist/commands/release.md +3 -3
- package/dist/commands/research.md +2 -2
- package/dist/commands/resolve.md +50 -46
- package/dist/commands/self-review.md +10 -9
- 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/area-pause-notice.js +44 -0
- package/dist/core/assets.js +126 -17
- package/dist/core/compliance-off-files.js +27 -0
- package/dist/core/curation-move-skip.js +4 -5
- package/dist/core/curation-queue-cleanup.js +6 -5
- package/dist/core/curation-store.js +1 -0
- package/dist/core/curation-variants.js +460 -112
- package/dist/core/evidence-policy.js +10 -8
- 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 +69 -12
- package/dist/core/tracked-areas.js +138 -14
- 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 +10 -10
- package/dist/curation-off/agents/fix.md +7 -7
- package/dist/curation-off/agents/harden.md +7 -6
- package/dist/curation-off/agents/refactor.md +6 -6
- package/dist/curation-off/agents/triage.md +7 -0
- package/dist/curation-off/commands/bug-analysis.md +69 -119
- package/dist/curation-off/commands/code-review.md +11 -11
- package/dist/curation-off/commands/debug.md +2 -2
- package/dist/curation-off/commands/dynamic-build.md +25 -25
- package/dist/curation-off/commands/explore.md +2 -2
- package/dist/curation-off/commands/implement.md +48 -34
- package/dist/curation-off/commands/plan.md +2 -2
- package/dist/curation-off/commands/release.md +1 -1
- package/dist/curation-off/commands/resolve.md +50 -46
- package/dist/curation-off/commands/self-review.md +10 -9
- package/dist/hud/components/curation-counts.js +27 -0
- package/dist/skills/git/references/pr/post-resolution-summary.md +12 -11
- package/dist/skills/git/references/pr/post-review-summary.md +12 -11
- package/dist/skills/git/references/pr/resolve-review-threads.md +1 -1
- package/dist/skills/git/references/pr/update-pr-evidence.md +2 -2
- package/dist/skills/git/references/publication-gate.md +6 -7
- package/dist/skills/git/references/tracker/github/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/jira/manage-debt.md +6 -2
- package/dist/skills/git/references/tracker/linear/manage-debt.md +6 -2
- 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/code.mds +4 -4
- package/src/assets/agents/evaluate.md +7 -13
- package/src/assets/agents/fix.mds +1 -1
- package/src/assets/agents/git.mds +34 -2
- package/src/assets/agents/harden.mds +7 -6
- package/src/assets/agents/skim.mds +1 -1
- package/src/assets/agents/test.md +21 -12
- package/src/assets/agents/triage.mds +9 -0
- package/src/assets/agents/validate.md +27 -34
- package/src/assets/commands/_partials/_compliance.mds +5 -3
- package/src/assets/commands/_partials/_decisions.mds +10 -8
- package/src/assets/commands/_partials/_engine.mds +9 -8
- package/src/assets/commands/_partials/_publication.mds +10 -1
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/bug-analysis.mds +80 -126
- package/src/assets/commands/code-review.mds +52 -9
- 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 +229 -44
- package/src/assets/commands/self-review.mds +8 -7
- package/src/assets/mds/git/_ops.mds +29 -1
- package/src/assets/mds/git/_ops_pr.mds +20 -10
- package/src/assets/mds/git/_ops_steps.mds +27 -0
- package/src/assets/mds/git/_pr.mds +15 -15
- package/src/assets/mds/git/_references.mds +12 -7
- package/src/assets/mds/tracker/_common.mds +4 -0
- package/src/assets/mds/tracker/_github.mds +43 -4
- package/src/assets/mds/tracker/_jira.mds +44 -4
- package/src/assets/mds/tracker/_linear.mds +44 -4
- package/src/assets/rules/quality.md +2 -0
- package/src/assets/scripts/ci-wait.cjs +493 -118
- 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/background-memory-update +79 -1
- package/src/assets/scripts/hooks/json-helper.cjs +44 -2
- package/src/assets/scripts/hooks/lib/curation-store.cjs +82 -20
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +9 -1
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +226 -33
- package/src/assets/scripts/hooks/memory-permission +10 -2
- package/src/assets/scripts/hooks/memory-worker +11 -0
- package/src/assets/scripts/hooks/pre-compact-memory +11 -0
- package/src/assets/scripts/hooks/preamble +18 -8
- package/src/assets/scripts/hooks/queue-append +218 -1
- package/src/assets/scripts/hooks/session-start-context +89 -37
- package/src/assets/scripts/hooks/session-start-memory +39 -0
- package/src/assets/scripts/hooks/session-start-orchestrator +4 -2
- package/src/assets/scripts/pr-evidence.cjs +46 -4
- package/src/assets/scripts/resolve-settings.cjs +244 -30
- package/src/assets/scripts/verify-evidence.cjs +708 -124
- package/src/assets/skills/agent-references/SKILL.md +7 -1
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +6 -4
- 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/commands/resolve-levels.md +3 -2
- package/src/assets/skills/agent-references/references/synthesize/bug-analysis.md +6 -2
- package/src/assets/skills/agent-references/references/synthesize/review.md +4 -4
- package/src/assets/skills/agent-references/references/triage/severity-tiers.md +43 -0
- package/src/assets/skills/agent-references/references/validate/passed-alone.md +14 -0
- 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
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
* devflow compliance — Enable, disable, set, and check status of the compliance feature.
|
|
3
3
|
*
|
|
4
4
|
* CLI-layer module; pure helpers in src/core/compliance.ts,
|
|
5
|
-
* I/O orchestration in src/targets/claude-code/
|
|
5
|
+
* I/O orchestration in src/targets/claude-code/curation-install.ts (the one converge
|
|
6
|
+
* for both axes) and compliance-install.ts (the compliance artifacts it composes).
|
|
6
7
|
* Compliance is manifest-group (like proxy), not config.json-gated.
|
|
7
8
|
* Per-artifact failures are warn-not-throw, so one failed artifact never aborts the rest.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
9
|
+
* Every change of the switch (enable, disable, --set, interactive enable) writes the switch,
|
|
10
|
+
* runs the one converge, and only after a clean converge records which variants are installed.
|
|
11
|
+
* With compliance off the command fails closed (AC-28): it exits non-zero when an installed
|
|
12
|
+
* prompt may still post, and prints no success line.
|
|
10
13
|
* The evidence-policy lines (--status, and the --enable/--set suggestion) come
|
|
11
14
|
* from src/core/evidence-policy.ts, the seam onto the package's own resolvers;
|
|
12
15
|
* the CLI prints the keys to add to .devflow/project.json and never writes it
|
|
@@ -21,7 +24,10 @@ import { COMPLIANCE_FRAMEWORKS, normalizeFrameworks, parseFrameworkList, } from
|
|
|
21
24
|
import { frameworkChoices, FRAMEWORK_SELECT_MESSAGE } from './compliance-prompts.js';
|
|
22
25
|
import { COMPLIANCE_SKILL_TOKENS } from '../../core/compliance-compose.js';
|
|
23
26
|
import { evidencePolicyStatusLine, evidencePolicySuggestion, formatEvidencePolicyUnavailable, loadEvidencePolicyModule, loadSettingsModule, repoComplianceStatusLines, } from '../../core/evidence-policy.js';
|
|
27
|
+
import { postingStatus, readInstalledVariants } from '../../core/installed-variants.js';
|
|
28
|
+
import { withManifestLock } from '../../core/manifest-lock.js';
|
|
24
29
|
import { readManifest, writeManifest } from '../../core/manifest.js';
|
|
30
|
+
import { applyVariantToggle, describePostingLeft, describeVariantConverge, readRunningVersion, } from '../../targets/claude-code/curation-install.js';
|
|
25
31
|
import { convergeFromManifest } from '../../targets/claude-code/compliance-install.js';
|
|
26
32
|
import { validateRuleShadow, validateSkillShadow } from '../../targets/claude-code/installer.js';
|
|
27
33
|
import { getClaudeDirectory, getDevFlowDirectory, } from '../../targets/claude-code/claude-paths.js';
|
|
@@ -67,7 +73,7 @@ export function resolveComplianceCliAction(current, action, setFrameworks) {
|
|
|
67
73
|
messages: [
|
|
68
74
|
{
|
|
69
75
|
level: 'success',
|
|
70
|
-
text: 'Compliance disabled — rule removed, frameworks remembered for re-enable',
|
|
76
|
+
text: 'Compliance disabled — the rule and every posting step removed, frameworks remembered for re-enable',
|
|
71
77
|
},
|
|
72
78
|
],
|
|
73
79
|
};
|
|
@@ -118,10 +124,12 @@ async function skillInstalled(claudeDir) {
|
|
|
118
124
|
return false;
|
|
119
125
|
}
|
|
120
126
|
}
|
|
127
|
+
/** The installed compliance rule file. */
|
|
128
|
+
const complianceRuleFile = (claudeDir) => path.join(claudeDir, 'rules', 'devflow', 'compliance.md');
|
|
121
129
|
/** Returns true if the compliance rule file exists at the install target. */
|
|
122
130
|
async function ruleInstalled(claudeDir) {
|
|
123
131
|
try {
|
|
124
|
-
await fs.access(
|
|
132
|
+
await fs.access(complianceRuleFile(claudeDir));
|
|
125
133
|
return true;
|
|
126
134
|
}
|
|
127
135
|
catch {
|
|
@@ -156,10 +164,146 @@ async function skillShadowState(devflowDir) {
|
|
|
156
164
|
return 'none';
|
|
157
165
|
}
|
|
158
166
|
}
|
|
167
|
+
/** The `Posting:` line of `--status`: the install record read against the machine's compliance switch (AC-31). */
|
|
168
|
+
export function postingStatusLine(record, complianceEnabled) {
|
|
169
|
+
const status = postingStatus(readInstalledVariants(record), complianceEnabled);
|
|
170
|
+
const shown = status === 'installed' ? color.green(status) : status === 'not installed' ? color.dim(status) : color.yellow(status);
|
|
171
|
+
return `Posting: ${shown}`;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Write the switch under the manifest lock, into the manifest read inside the lock, so a field
|
|
175
|
+
* that another devflow run wrote since this command first read the manifest is kept. A lock that
|
|
176
|
+
* cannot be taken, or is lost before the write, writes nothing. Null when the switch is on disk,
|
|
177
|
+
* otherwise the reason it is not. Never throws.
|
|
178
|
+
*/
|
|
179
|
+
async function recordComplianceSwitch(devflowDir, nextState) {
|
|
180
|
+
try {
|
|
181
|
+
const locked = await withManifestLock(devflowDir, {}, async (lock) => {
|
|
182
|
+
const current = await readManifest(devflowDir);
|
|
183
|
+
if (current === null)
|
|
184
|
+
return 'no readable install manifest';
|
|
185
|
+
if (!(await lock.stillHeld()))
|
|
186
|
+
return 'the install manifest lock was lost';
|
|
187
|
+
await writeManifest(devflowDir, {
|
|
188
|
+
...current,
|
|
189
|
+
features: { ...current.features, compliance: nextState },
|
|
190
|
+
updatedAt: new Date().toISOString(),
|
|
191
|
+
});
|
|
192
|
+
return null;
|
|
193
|
+
});
|
|
194
|
+
return locked.ok ? locked.value : 'the install manifest lock could not be taken';
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
return String(err);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Apply one change of the compliance switch: write the switch, run the one converge for both
|
|
202
|
+
* axes, and only after a clean converge record which variants are installed (the toggle helper
|
|
203
|
+
* does that last step). Every change of the enabled value converges, `--set` and interactive enable
|
|
204
|
+
* included, and a `--set` that only changes the frameworks converges too, so the stamp follows.
|
|
205
|
+
*
|
|
206
|
+
* Fail closed on the off side: with compliance off, a converge that cannot prove every installed
|
|
207
|
+
* compliance-armed prompt holds the compliance-off file is an error that lists the files, a
|
|
208
|
+
* toggle that cannot converge at all (a version skew, or no manifest) says that posting steps remain
|
|
209
|
+
* installed until `devflow init`, and a compliance rule that is still installed is an error that
|
|
210
|
+
* names the file. None of them prints the success text. A skipped prompt converge still converges
|
|
211
|
+
* the compliance skill and rule, which do not depend on the installed version. On the enable side
|
|
212
|
+
* the skip is a warning, and a rule that is wanted and still not installed is a second warning.
|
|
213
|
+
* A reference that the converge could not refresh, a linked folder for example, is an error that
|
|
214
|
+
* names it, with exit 1, and the switch stays recorded as on.
|
|
215
|
+
*
|
|
216
|
+
* Returns a result and never throws; the caller prints the messages and sets the exit code.
|
|
217
|
+
*/
|
|
218
|
+
export async function applyComplianceChange(opts) {
|
|
219
|
+
const { claudeDir, devflowDir, manifest, action, setFrameworks, runningVersion, packageRoot, warn } = opts;
|
|
220
|
+
const resolved = resolveComplianceCliAction(manifest.features.compliance, action, setFrameworks);
|
|
221
|
+
// normalizeFrameworks deduplicates (e.g. --set gdpr,GDPR → [gdpr]).
|
|
222
|
+
const nextState = {
|
|
223
|
+
enabled: resolved.nextState.enabled,
|
|
224
|
+
frameworks: normalizeFrameworks(resolved.nextState.frameworks),
|
|
225
|
+
};
|
|
226
|
+
// The switch comes first: whatever the converge does next, the machine's choice is on disk.
|
|
227
|
+
const notRecorded = await recordComplianceSwitch(devflowDir, nextState);
|
|
228
|
+
if (notRecorded !== null) {
|
|
229
|
+
return {
|
|
230
|
+
nextState: manifest.features.compliance,
|
|
231
|
+
messages: [{ level: 'error', text: `Could not record the compliance switch, so nothing was changed: ${notRecorded}` }],
|
|
232
|
+
exitCode: 1,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const outcome = await applyVariantToggle({
|
|
236
|
+
claudeDir,
|
|
237
|
+
devflowDir,
|
|
238
|
+
axis: 'compliance',
|
|
239
|
+
enabled: nextState.enabled,
|
|
240
|
+
runningVersion,
|
|
241
|
+
warn,
|
|
242
|
+
packageRoot,
|
|
243
|
+
});
|
|
244
|
+
if (outcome.kind === 'skipped') {
|
|
245
|
+
// The compliance skill and rule do not depend on the installed version, so they converge
|
|
246
|
+
// whatever the prompt converge did. The converge warns per item and never throws.
|
|
247
|
+
await convergeFromManifest({
|
|
248
|
+
claudeDir,
|
|
249
|
+
devflowDir,
|
|
250
|
+
manifest: { features: { compliance: nextState, rules: manifest.features.rules } },
|
|
251
|
+
warn,
|
|
252
|
+
});
|
|
253
|
+
if (!nextState.enabled) {
|
|
254
|
+
return { nextState, messages: [{ level: 'error', text: outcome.message }], exitCode: 1 };
|
|
255
|
+
}
|
|
256
|
+
const ruleNote = manifest.features.rules && !(await ruleInstalled(claudeDir))
|
|
257
|
+
? [{ level: 'warn', text: 'The compliance rule was not installed. Run `devflow init`.' }]
|
|
258
|
+
: [];
|
|
259
|
+
return { nextState, messages: [...resolved.messages, ...ruleNote, { level: 'warn', text: outcome.message }], exitCode: 0 };
|
|
260
|
+
}
|
|
261
|
+
const { posting } = outcome.result;
|
|
262
|
+
if (!nextState.enabled && posting.kind !== 'clear') {
|
|
263
|
+
return {
|
|
264
|
+
nextState,
|
|
265
|
+
messages: [
|
|
266
|
+
{ level: 'error', text: 'Compliance is recorded as off, but the install may still post to GitHub or a tracker.' },
|
|
267
|
+
...describePostingLeft(posting).map((text) => ({ level: 'error', text })),
|
|
268
|
+
],
|
|
269
|
+
exitCode: 1,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
// A failed rule removal is only a warning inside the converge. The line that says the rule is
|
|
273
|
+
// removed prints only when it is gone.
|
|
274
|
+
if (!nextState.enabled && (await ruleInstalled(claudeDir))) {
|
|
275
|
+
return {
|
|
276
|
+
nextState,
|
|
277
|
+
messages: [{
|
|
278
|
+
level: 'error',
|
|
279
|
+
text: `Compliance is recorded as off, but the compliance rule could not be removed: ${complianceRuleFile(claudeDir)}. ` +
|
|
280
|
+
'Fix the cause in the warning above, or delete the file. Then run `devflow compliance --disable` again.',
|
|
281
|
+
}],
|
|
282
|
+
exitCode: 1,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
const referenceFailures = outcome.result.references.failures;
|
|
286
|
+
if (nextState.enabled && referenceFailures.length > 0) {
|
|
287
|
+
return {
|
|
288
|
+
nextState,
|
|
289
|
+
messages: [{
|
|
290
|
+
level: 'error',
|
|
291
|
+
text: 'Compliance is recorded as on, but some installed references were not refreshed: ' +
|
|
292
|
+
`${referenceFailures.join('; ')}. Replace any link in the cause with a real folder. Then run the command again.`,
|
|
293
|
+
}],
|
|
294
|
+
exitCode: 1,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const messages = [...resolved.messages];
|
|
298
|
+
const changed = describeVariantConverge(outcome.result, 'compliance', nextState.enabled);
|
|
299
|
+
if (changed !== null)
|
|
300
|
+
messages.push({ level: 'info', text: `Installed prompts: ${changed}` });
|
|
301
|
+
return { nextState, messages, exitCode: 0 };
|
|
302
|
+
}
|
|
159
303
|
export const complianceCommand = new Command('compliance')
|
|
160
304
|
.description('Enable, disable, or configure the compliance feature')
|
|
161
|
-
.option('--enable', 'Enable compliance (restores previously selected frameworks)')
|
|
162
|
-
.option('--disable', 'Disable compliance (rule removed; frameworks remembered for re-enable)')
|
|
305
|
+
.option('--enable', 'Enable compliance (restores previously selected frameworks and installs the steps that post evidence and summaries to GitHub or a tracker)')
|
|
306
|
+
.option('--disable', 'Disable compliance (rule and every step that posts evidence or summaries to GitHub or a tracker removed; frameworks remembered for re-enable)')
|
|
163
307
|
.option('--status', 'Show compliance state: manifest, installed artifacts, shadow presence, and the evidence policy for the current repository')
|
|
164
308
|
.option('--set <list>', 'Set active frameworks (comma-separated IDs); enables compliance. Use --set "" for zero frameworks (generic controls only)')
|
|
165
309
|
.action(async (options) => {
|
|
@@ -169,8 +313,8 @@ export const complianceCommand = new Command('compliance')
|
|
|
169
313
|
if (!hasFlag) {
|
|
170
314
|
p.intro(color.bgCyan(color.white(' Compliance ')));
|
|
171
315
|
const validIds = COMPLIANCE_FRAMEWORKS.map(fw => `${fw.id} — ${fw.hint}`).join('\n ');
|
|
172
|
-
p.note(`${color.cyan('devflow compliance --enable')} Enable compliance (restores prior frameworks)\n` +
|
|
173
|
-
`${color.cyan('devflow compliance --disable')} Disable compliance (frameworks remembered)\n` +
|
|
316
|
+
p.note(`${color.cyan('devflow compliance --enable')} Enable compliance (restores prior frameworks; installs the posting steps)\n` +
|
|
317
|
+
`${color.cyan('devflow compliance --disable')} Disable compliance (removes the posting steps; frameworks remembered)\n` +
|
|
174
318
|
`${color.cyan('devflow compliance --status')} Show current state and installed artifacts\n` +
|
|
175
319
|
`${color.cyan('devflow compliance --set <ids>')} Set active frameworks and enable\n\n` +
|
|
176
320
|
`Valid framework IDs:\n ${validIds}`, 'Usage');
|
|
@@ -228,6 +372,7 @@ export const complianceCommand = new Command('compliance')
|
|
|
228
372
|
? color.yellow(' (withheld — rules disabled)')
|
|
229
373
|
: '') +
|
|
230
374
|
(isRuleShadowed ? color.green(' [shadowed]') : ''),
|
|
375
|
+
postingStatusLine(manifest.installedVariants, current.enabled),
|
|
231
376
|
'',
|
|
232
377
|
// The repository in cwd, resolved by the package's own resolver with the
|
|
233
378
|
// compliance state already read above (D-POLICY-CJS-SEAM). Bounded: at most
|
|
@@ -277,23 +422,18 @@ export const complianceCommand = new Command('compliance')
|
|
|
277
422
|
else {
|
|
278
423
|
action = 'disable';
|
|
279
424
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
await convergeFromManifest({
|
|
425
|
+
// Write the switch, converge both axes, record the installed variants (applyComplianceChange).
|
|
426
|
+
const applied = await applyComplianceChange({
|
|
283
427
|
claudeDir,
|
|
284
428
|
devflowDir,
|
|
285
|
-
manifest
|
|
429
|
+
manifest,
|
|
430
|
+
action,
|
|
431
|
+
setFrameworks,
|
|
432
|
+
runningVersion: await readRunningVersion(),
|
|
286
433
|
warn: (msg) => p.log.warn(msg),
|
|
287
434
|
});
|
|
288
|
-
// Persist to manifest — normalizeFrameworks deduplicates (e.g. --set gdpr,GDPR → [gdpr])
|
|
289
|
-
manifest.features.compliance = {
|
|
290
|
-
enabled: resolved.nextState.enabled,
|
|
291
|
-
frameworks: normalizeFrameworks(resolved.nextState.frameworks),
|
|
292
|
-
};
|
|
293
|
-
manifest.updatedAt = new Date().toISOString();
|
|
294
|
-
await writeManifest(devflowDir, manifest);
|
|
295
435
|
// Emit messages
|
|
296
|
-
for (const msg of
|
|
436
|
+
for (const msg of applied.messages) {
|
|
297
437
|
switch (msg.level) {
|
|
298
438
|
case 'success':
|
|
299
439
|
p.log.success(msg.text);
|
|
@@ -301,18 +441,25 @@ export const complianceCommand = new Command('compliance')
|
|
|
301
441
|
case 'warn':
|
|
302
442
|
p.log.warn(msg.text);
|
|
303
443
|
break;
|
|
444
|
+
case 'error':
|
|
445
|
+
p.log.error(msg.text);
|
|
446
|
+
break;
|
|
304
447
|
default:
|
|
305
448
|
p.log.info(msg.text);
|
|
306
449
|
break;
|
|
307
450
|
}
|
|
308
451
|
}
|
|
309
|
-
if (
|
|
452
|
+
if (applied.exitCode !== 0) {
|
|
453
|
+
process.exitCode = applied.exitCode;
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
if (applied.nextState.enabled && !manifest.features.rules) {
|
|
310
457
|
p.log.info(color.dim('Note: compliance rule withheld (rules disabled) — ' +
|
|
311
458
|
'run `devflow rules --enable` to install the stamped rule'));
|
|
312
459
|
}
|
|
313
460
|
// Suggest the team file compliance now implies. Printed, never written:
|
|
314
461
|
// .devflow/project.json is team-owned (D-POLICY-NO-WRITE).
|
|
315
|
-
if (
|
|
462
|
+
if (applied.nextState.enabled) {
|
|
316
463
|
const policyModule = loadEvidencePolicyModule();
|
|
317
464
|
const settingsModule = loadSettingsModule();
|
|
318
465
|
if (!policyModule.ok) {
|
|
@@ -322,7 +469,7 @@ export const complianceCommand = new Command('compliance')
|
|
|
322
469
|
p.log.warn(formatEvidencePolicyUnavailable(settingsModule.error));
|
|
323
470
|
}
|
|
324
471
|
else {
|
|
325
|
-
const suggestion = evidencePolicySuggestion(
|
|
472
|
+
const suggestion = evidencePolicySuggestion(applied.nextState, policyModule.value, settingsModule.value);
|
|
326
473
|
if (suggestion !== null)
|
|
327
474
|
p.note(suggestion, 'Evidence policy');
|
|
328
475
|
}
|
|
@@ -9,8 +9,10 @@ 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
|
+
import { AREA_PAUSE_NOTICE } from '../../core/area-pause-notice.js';
|
|
15
|
+
import { probeTrackedArea } from '../../core/tracked-areas.js';
|
|
14
16
|
import { drainCurationQueue, drainDisabledCurationQueue, formatCurationNotDrained } from '../../core/curation-queue-cleanup.js';
|
|
15
17
|
import { firstSymbolicLink } from '../../core/linked-path.js';
|
|
16
18
|
import { describeMoveText, describeOutcome } from './curation-move-step.js';
|
|
@@ -74,10 +76,34 @@ function requireStore() {
|
|
|
74
76
|
process.exitCode = 1;
|
|
75
77
|
return null;
|
|
76
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Which fixed notice a store refusal of kind `tracked` takes: the reason the store names
|
|
81
|
+
* on the refusal. A refusal that names none is `unknown`, the notice that claims least.
|
|
82
|
+
*/
|
|
83
|
+
function pauseReasonOf(error) {
|
|
84
|
+
return error.reason ?? 'unknown';
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The fixed line of a paused curation area at `ledgerRoot`, or null when git tracks
|
|
88
|
+
* nothing in the curation folders there. The area is paused when git tracks files in the
|
|
89
|
+
* curation folder, the old ledger folder or a backup folder, or when a `.git` entry stands
|
|
90
|
+
* and git did not answer; a root with no `.git` entry makes no git call. Every command
|
|
91
|
+
* asks first, before it reads a ledger file, takes a lock or prompts.
|
|
92
|
+
*/
|
|
93
|
+
async function pausedCurationLine(ledgerRoot) {
|
|
94
|
+
const { answer } = await probeTrackedArea(ledgerRoot, 'curation');
|
|
95
|
+
return answer === 'untracked' ? null : AREA_PAUSE_NOTICE.curation[answer];
|
|
96
|
+
}
|
|
97
|
+
/** Say that a command changed nothing because the curation area is paused: the fixed `line`, exit code 1. */
|
|
98
|
+
function refusePaused(line) {
|
|
99
|
+
p.log.error(line);
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
}
|
|
77
102
|
/** What a writer says when the store refused; `undone` completes "Nothing was …". */
|
|
78
103
|
function storeRefusal(error, undone) {
|
|
79
104
|
switch (error.kind) {
|
|
80
105
|
case 'no-curation-dir': return NO_CURATION_DATA;
|
|
106
|
+
case 'tracked': return AREA_PAUSE_NOTICE.curation[pauseReasonOf(error)];
|
|
81
107
|
case 'busy': return `The curation store is busy: another run holds its lock. Nothing was ${undone}; try again in a moment.`;
|
|
82
108
|
default: return error.message;
|
|
83
109
|
}
|
|
@@ -143,15 +169,21 @@ async function moveReadNotice(ledgerRoot) {
|
|
|
143
169
|
}
|
|
144
170
|
/**
|
|
145
171
|
* The gate every writing subcommand passes first (--restore, --clear, --reset and the
|
|
146
|
-
* project scope of --configure).
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
172
|
+
* project scope of --configure). A paused curation area (git tracks files in a data
|
|
173
|
+
* folder, or git did not answer) refuses with the fixed notice before anything else, so
|
|
174
|
+
* a clear or a reset never reaches its confirm prompt. Installed scripts of another
|
|
175
|
+
* version read and create another ledger folder, so they refuse with the skew line.
|
|
176
|
+
* Then, where the old folder is present, the shared move runs with no wait: a busy lock
|
|
177
|
+
* prints a retry message, a queue claim that holds the move prints its own message, and
|
|
178
|
+
* a refused or failed move prints its reason; in each case nothing is written.
|
|
152
179
|
* `undone` completes "Nothing was …". Answers true when the caller may write.
|
|
153
180
|
*/
|
|
154
181
|
async function prepareToWrite(ledgerRoot, undone) {
|
|
182
|
+
const paused = await pausedCurationLine(ledgerRoot);
|
|
183
|
+
if (paused !== null) {
|
|
184
|
+
refusePaused(paused);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
155
187
|
const skew = await installedScriptsSkew(getDevFlowDirectory());
|
|
156
188
|
if (skew !== null) {
|
|
157
189
|
p.log.error(skew);
|
|
@@ -267,7 +299,8 @@ function backupLines(ledgerRoot) {
|
|
|
267
299
|
* whose ledger still sits in the old folder gets the HUD's pointer (or refused line, or
|
|
268
300
|
* the notice of a skipped move) in place of the counts; a project with both folders and
|
|
269
301
|
* a skipped move gets the notice with the counts; a project with backup folders gets one
|
|
270
|
-
* line for each.
|
|
302
|
+
* line for each. A paused area (git tracks files in a data folder, or did not answer)
|
|
303
|
+
* gets the fixed notice after the machine switch, nothing is read, and the exit code is 0.
|
|
271
304
|
*/
|
|
272
305
|
async function handleStatus() {
|
|
273
306
|
// D-FEATURES-NARROW-ONLY: the machine switch is the manifest's and reads the
|
|
@@ -282,6 +315,11 @@ async function handleStatus() {
|
|
|
282
315
|
if (trackedWarning !== null)
|
|
283
316
|
p.log.warn(trackedWarning);
|
|
284
317
|
const ledgerRoot = await getLedgerRoot();
|
|
318
|
+
const paused = await pausedCurationLine(ledgerRoot ?? process.cwd());
|
|
319
|
+
if (paused !== null) {
|
|
320
|
+
p.log.info(`${stateLine}\n${paused}`);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
285
323
|
if (!ledgerRoot) {
|
|
286
324
|
p.log.info(`${stateLine}\nEntries: not in a git project`);
|
|
287
325
|
return;
|
|
@@ -312,13 +350,19 @@ async function handleStatus() {
|
|
|
312
350
|
}
|
|
313
351
|
/**
|
|
314
352
|
* `--list`: the store's listing — the same text json-helper's `list` op prints —
|
|
315
|
-
* on stdout. Read-only. Outside a git project it reads the current directory.
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
353
|
+
* on stdout. Read-only. Outside a git project it reads the current directory. A paused
|
|
354
|
+
* area prints the fixed notice and exits 1, and reads nothing. Where the ledger still
|
|
355
|
+
* sits in the old folder it prints the pointer, the refused line or the notice of a
|
|
356
|
+
* skipped move in place of the listing; with both folders and a skipped move it prints
|
|
357
|
+
* the notice before the listing.
|
|
319
358
|
*/
|
|
320
359
|
async function handleList() {
|
|
321
360
|
const root = (await getLedgerRoot()) ?? process.cwd();
|
|
361
|
+
const paused = await pausedCurationLine(root);
|
|
362
|
+
if (paused !== null) {
|
|
363
|
+
refusePaused(paused);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
322
366
|
const notice = await moveReadNotice(root);
|
|
323
367
|
if (notice !== null)
|
|
324
368
|
p.log.info(notice.line);
|
|
@@ -342,12 +386,18 @@ async function handleList() {
|
|
|
342
386
|
/**
|
|
343
387
|
* `--show <id>`: one entry, by its anchor or its observation id, as the JSON
|
|
344
388
|
* json-helper's `show` op prints, made terminal-safe. Read-only. Outside a git
|
|
345
|
-
* project it reads the current directory.
|
|
346
|
-
*
|
|
347
|
-
*
|
|
389
|
+
* project it reads the current directory. A paused area prints the fixed notice as the
|
|
390
|
+
* refusal and reads nothing. Where the ledger still sits in the old folder it prints the
|
|
391
|
+
* pointer, the refused line or the notice of a skipped move as the refusal; with both
|
|
392
|
+
* folders and a skipped move it prints the notice on stderr and goes on.
|
|
348
393
|
*/
|
|
349
394
|
async function handleShow(key) {
|
|
350
395
|
const root = (await getLedgerRoot()) ?? process.cwd();
|
|
396
|
+
const paused = await pausedCurationLine(root);
|
|
397
|
+
if (paused !== null) {
|
|
398
|
+
refusePaused(paused);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
351
401
|
const notice = await moveReadNotice(root);
|
|
352
402
|
if (notice?.kind === 'instead') {
|
|
353
403
|
p.log.error(notice.line);
|
|
@@ -593,13 +643,15 @@ async function handleClear() {
|
|
|
593
643
|
/**
|
|
594
644
|
* Make the installed prompts match the switch just written
|
|
595
645
|
* (D-LEARNING-VARIANT-INSTALL): the curation-on or curation-off commands and
|
|
596
|
-
* agents, and the apply-decisions skill. The
|
|
597
|
-
*
|
|
598
|
-
* 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.
|
|
599
649
|
*
|
|
600
650
|
* Warn-not-fail: the switch itself is already recorded, and either mismatch is
|
|
601
651
|
* safe (an on variant is still gated at run time, an off variant loads no
|
|
602
|
-
* 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.
|
|
603
655
|
*
|
|
604
656
|
* Answers whether the converge ran, which means the installed files are the ones
|
|
605
657
|
* this CLI writes. The toggle moves the ledger folder only then: under version skew
|
|
@@ -607,10 +659,11 @@ async function handleClear() {
|
|
|
607
659
|
*/
|
|
608
660
|
async function convergeInstalledVariants(enabled) {
|
|
609
661
|
try {
|
|
610
|
-
const outcome = await
|
|
662
|
+
const outcome = await applyVariantToggle({
|
|
611
663
|
claudeDir: getClaudeDirectory(),
|
|
612
664
|
devflowDir: getDevFlowDirectory(),
|
|
613
|
-
|
|
665
|
+
axis: 'curation',
|
|
666
|
+
enabled,
|
|
614
667
|
runningVersion: await readRunningVersion(),
|
|
615
668
|
warn: (msg) => p.log.warn(msg),
|
|
616
669
|
});
|
|
@@ -621,6 +674,8 @@ async function convergeInstalledVariants(enabled) {
|
|
|
621
674
|
const changed = describeCurationConverge(outcome.result, enabled);
|
|
622
675
|
if (changed !== null)
|
|
623
676
|
p.log.info(color.dim(`Installed prompts: ${changed}`));
|
|
677
|
+
for (const line of describePostingLeft(outcome.result.posting))
|
|
678
|
+
p.log.warn(line);
|
|
624
679
|
return 'converged';
|
|
625
680
|
}
|
|
626
681
|
catch (err) {
|
|
@@ -657,8 +712,9 @@ async function tryMoveForToggle(ledgerRoot) {
|
|
|
657
712
|
* variants and skill (D-LEARNING-VARIANT-INSTALL), the current project's ledger
|
|
658
713
|
* folder move, and on disable a drained queue in every ledger folder that exists and
|
|
659
714
|
* in the backups a move left. The drain deletes no queue file, and prints the fixed
|
|
660
|
-
* notice of
|
|
661
|
-
* backup folder, or does not answer.
|
|
715
|
+
* pause notice of the curation area, when git tracks any entry under a data folder or a
|
|
716
|
+
* backup folder, or does not answer. A move that has just printed its own notice for the
|
|
717
|
+
* same reason leaves the drain's line short, so the cause is told once.
|
|
662
718
|
* Never requires a git root: the switch is not a per-project setting.
|
|
663
719
|
*
|
|
664
720
|
* Under version skew the toggle records the switch and moves nothing; otherwise it
|
|
@@ -31,21 +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 clause that lean skips Harden on a
|
|
42
|
-
* docs-only diff has no facet row in `tests/gate-level-prompts.test.ts`: the docs guard
|
|
43
|
-
* checks it at each doc site, against the row in the /implement 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.
|
|
44
41
|
*/
|
|
45
42
|
export const GATE_LEVEL_HINTS = Object.freeze({
|
|
46
|
-
full: 'the most checking: every review focus,
|
|
47
|
-
standard: 'fewer review focuses
|
|
48
|
-
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',
|
|
49
46
|
});
|
|
50
47
|
/** Build clack select options for the gate level list, most to least checking. */
|
|
51
48
|
export function gateLevelChoices() {
|
|
@@ -53,12 +50,24 @@ export function gateLevelChoices() {
|
|
|
53
50
|
}
|
|
54
51
|
/**
|
|
55
52
|
* Format a padded level catalogue suitable for a clack note body.
|
|
56
|
-
* Produces: ` standard fewer review focuses
|
|
53
|
+
* Produces: ` standard fewer review focuses, no Polish; the default`
|
|
57
54
|
*/
|
|
58
55
|
export function formatGateLevelCatalogue() {
|
|
59
56
|
const width = Math.max(...GATE_LEVELS.map(level => level.length));
|
|
60
57
|
return 'Levels:\n' + GATE_LEVELS.map(level => ` ${level.padEnd(width)} ${GATE_LEVEL_HINTS[level]}`).join('\n');
|
|
61
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']);
|
|
62
71
|
/** `features.gateLevel` of a raw manifest, exactly as stored; `undefined` for an absent key. */
|
|
63
72
|
function rawGateLevelOf(raw) {
|
|
64
73
|
const features = raw.features;
|
|
@@ -226,17 +235,24 @@ export function describeGateLevel(stored) {
|
|
|
226
235
|
export function gateLevelPromptSeed(stored, origin = 'manifest') {
|
|
227
236
|
return appliedLevel(classifyGateLevel(stored, origin));
|
|
228
237
|
}
|
|
229
|
-
/** 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. */
|
|
230
239
|
function currentSettingLines(stored, origin) {
|
|
231
240
|
switch (origin) {
|
|
232
241
|
case 'manifest':
|
|
233
242
|
return `Current setting: ${describeGateLevel(stored)}`;
|
|
234
243
|
case 'rejected':
|
|
235
|
-
return
|
|
236
|
-
|
|
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');
|
|
237
250
|
case 'unreadable':
|
|
238
|
-
return
|
|
239
|
-
|
|
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');
|
|
240
256
|
default: {
|
|
241
257
|
const exhaustive = origin;
|
|
242
258
|
return exhaustive;
|
|
@@ -318,10 +334,10 @@ export function buildClackGateLevelPrompts() {
|
|
|
318
334
|
export async function runGateLevelStep(opts) {
|
|
319
335
|
const { stored, storedOrigin = 'manifest', prompts } = opts;
|
|
320
336
|
prompts.note(`${currentSettingLines(stored, storedOrigin)}\n\n` +
|
|
321
|
-
'Sets how much checking /code-review, /plan
|
|
322
|
-
'machine. A personal "gateLevel" in a repository\'s
|
|
323
|
-
'overrides it in that repository. Change it later
|
|
324
|
-
'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' +
|
|
325
341
|
formatGateLevelCatalogue(), 'Gate level');
|
|
326
342
|
const outcome = await prompts.selectLevel({
|
|
327
343
|
message: GATE_LEVEL_SELECT_MESSAGE,
|
|
@@ -39,17 +39,16 @@ 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
|
-
* `tests/gate-level-prompts.test.ts`: the docs guard checks it at each doc site, against
|
|
50
|
-
* the row in the /implement level reference.
|
|
49
|
+
* skip Polish).
|
|
51
50
|
*/
|
|
52
|
-
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.';
|
|
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.';
|
|
53
52
|
const MACHINE_MANIFEST_NAME = 'manifest.json';
|
|
54
53
|
const MACHINE_INVALID_LINE = `invalid (${MACHINE_MANIFEST_NAME}; reads as ${MALFORMED_GATE_LEVEL_READS_AS})`;
|
|
55
54
|
const VALID_LEVELS = GATE_LEVELS.join(', ');
|