devflow-kit 2.4.0 → 2.5.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 +156 -0
- package/README.md +86 -18
- package/dist/agents/git.md +824 -0
- package/dist/cli/commands/agents.js +6 -1
- package/dist/cli/commands/attribution-prompts.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +1 -1
- package/dist/cli/commands/compliance.js +23 -1
- package/dist/cli/commands/init-seed.js +24 -26
- package/dist/cli/commands/init.js +502 -71
- package/dist/cli/commands/install-report.js +205 -0
- package/dist/cli/commands/knowledge/index.js +2 -2
- package/dist/cli/commands/knowledge/toggle.js +27 -37
- package/dist/cli/commands/learning.js +37 -30
- package/dist/cli/commands/memory.js +79 -69
- package/dist/cli/commands/prompt-io.js +4 -4
- package/dist/cli/commands/security.js +76 -16
- package/dist/cli/commands/skills.js +53 -7
- package/dist/cli/commands/tracker-prompts.js +145 -0
- package/dist/cli/commands/tracker.js +405 -0
- package/dist/cli/commands/uninstall.js +211 -65
- package/dist/cli.js +2 -0
- package/dist/commands/bug-analysis.md +22 -4
- package/dist/commands/code-review.md +44 -15
- package/dist/commands/debug.md +20 -6
- package/dist/commands/dynamic-build.md +289 -67
- package/dist/commands/dynamic-plan.md +60 -21
- package/dist/commands/dynamic-profile.md +1 -1
- package/dist/commands/dynamic-tickets.md +58 -8
- package/dist/commands/explore.md +2 -2
- package/dist/commands/implement.md +241 -53
- package/dist/commands/plan.md +88 -17
- package/dist/commands/release.md +64 -17
- package/dist/commands/resolve.md +138 -58
- package/dist/commands/self-review.md +2 -2
- package/dist/core/agent-models.js +55 -12
- package/dist/core/assets.js +58 -2
- package/dist/core/evidence-policy.js +147 -0
- package/dist/core/feature-config.js +130 -64
- package/dist/core/feature-switch.js +112 -0
- package/dist/core/flags.js +4 -4
- package/dist/core/manifest.js +33 -7
- package/dist/core/mds-variants.js +861 -0
- package/dist/core/model-discovery.js +12 -1
- package/dist/core/plugins.js +357 -9
- package/dist/core/project-paths.js +1 -1
- 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/tracker.js +407 -0
- 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/installer.js +1213 -31
- package/dist/targets/claude-code/legacy.js +5 -0
- package/dist/targets/claude-code/post-install.js +196 -74
- package/dist/targets/claude-code/tracker-install.js +161 -0
- package/package.json +4 -3
- package/src/assets/agents/code.md +42 -4
- package/src/assets/agents/design.md +1 -1
- package/src/assets/agents/git.mds +827 -0
- package/src/assets/agents/knowledge.md +1 -1
- package/src/assets/agents/learning.md +11 -0
- package/src/assets/agents/synthesize.md +1 -1
- package/src/assets/agents/test.md +16 -5
- package/src/assets/agents/tracker.md +467 -0
- package/src/assets/agents/validate.md +7 -5
- package/src/assets/commands/_partials/_engine.mds +11 -9
- package/src/assets/commands/_partials/_evidence_policy.mds +30 -0
- package/src/assets/commands/_partials/_knowledge.mds +2 -2
- package/src/assets/commands/_partials/_plan_contract.mds +22 -7
- package/src/assets/commands/_partials/_preamble.mds +1 -1
- package/src/assets/commands/_partials/_publication.mds +3 -1
- 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 +15 -5
- package/src/assets/commands/code-review.mds +34 -14
- package/src/assets/commands/debug.mds +11 -4
- package/src/assets/commands/dynamic-build.mds +227 -41
- package/src/assets/commands/dynamic-plan.mds +35 -13
- package/src/assets/commands/dynamic-tickets.mds +47 -5
- package/src/assets/commands/implement.mds +206 -52
- package/src/assets/commands/plan.mds +70 -17
- package/src/assets/commands/release.md +64 -17
- package/src/assets/commands/resolve.mds +126 -56
- 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 +299 -0
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +5 -8
- package/src/assets/scripts/hooks/background-memory-update +14 -9
- package/src/assets/scripts/hooks/capture-prompt +6 -2
- package/src/assets/scripts/hooks/capture-question +6 -2
- package/src/assets/scripts/hooks/capture-turn +6 -2
- package/src/assets/scripts/hooks/ensure-devflow-init +1 -1
- package/src/assets/scripts/hooks/ensure-root-gitignore +161 -60
- package/src/assets/scripts/hooks/hook-log-init +3 -1
- package/src/assets/scripts/hooks/json-helper.cjs +223 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +1 -1
- package/src/assets/scripts/hooks/memory-worker +15 -8
- package/src/assets/scripts/hooks/pre-compact-memory +12 -8
- package/src/assets/scripts/hooks/preamble +1 -4
- package/src/assets/scripts/hooks/queue-append +68 -24
- package/src/assets/scripts/hooks/session-start-context +355 -8
- package/src/assets/scripts/hooks/session-start-memory +12 -8
- 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 +1065 -0
- package/src/assets/scripts/verify-evidence.cjs +1822 -0
- package/src/assets/skills/compliance/SKILL.md +2 -0
- package/src/assets/skills/docs-framework/SKILL.md +5 -3
- 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/agents/git.md +0 -938
|
@@ -6,7 +6,9 @@ import * as p from '@clack/prompts';
|
|
|
6
6
|
import color from 'picocolors';
|
|
7
7
|
import { getInstallationPaths, getClaudeDirectory, getManagedSettingsPath } from '../../targets/claude-code/claude-paths.js';
|
|
8
8
|
import { getGitRoot } from '../../core/git.js';
|
|
9
|
-
import { DEVFLOW_PLUGINS, SKILL_NAMESPACE, getAllSkillNames, getAllAgentNames, getAllCommandNames, parsePluginSelection, resolveFeatureRedirect, prefixSkillName, unprefixSkillName, FEATURE_OWNED_SKILLS } from '../../core/plugins.js';
|
|
9
|
+
import { DEVFLOW_PLUGINS, SKILL_NAMESPACE, getAllSkillNames, getAllAgentNames, getAllCommandNames, parsePluginSelection, resolveFeatureRedirect, prefixSkillName, unprefixSkillName, skillsOf, FEATURE_OWNED_SKILLS } from '../../core/plugins.js';
|
|
10
|
+
import { readManifest } from '../../core/manifest.js';
|
|
11
|
+
import { TRACKER_ATTEMPTS_FILE, TRACKER_CLAIM_FILE, TRACKER_ENABLED_FILE } from '../../core/tracker.js';
|
|
10
12
|
import { sweepOrphanedAssets, mdFileName, mdEntryName } from '../../core/orphan-sweep.js';
|
|
11
13
|
import { LEGACY_SKILL_NAMES } from '../../targets/claude-code/legacy.js';
|
|
12
14
|
import { removeAmbientHook } from './ambient.js';
|
|
@@ -18,6 +20,7 @@ import { removeContextHook } from './context.js';
|
|
|
18
20
|
import { applyProxyTeardownToSettings } from './proxy.js';
|
|
19
21
|
import { readProxyState, proxyJsonExists } from '../../core/proxy-state.js';
|
|
20
22
|
import { hudCacheDir } from '../../core/cache.js';
|
|
23
|
+
import { TRACKER_CONVENTIONS_FILE, TRACKER_CONVENTIONS_BACKUP_NAMES, TRACKER_STAGED_PREFIX, } from '../../core/tracker.js';
|
|
21
24
|
import { revertExternalAgents } from '../../core/agent-models.js';
|
|
22
25
|
import { detectShell, getProfilePath } from '../../core/safe-delete.js';
|
|
23
26
|
import { isAlreadyInstalled, removeFromProfile } from '../../core/safe-delete-install.js';
|
|
@@ -26,20 +29,51 @@ import { writeFileAtomicExclusive } from '../../core/fs-atomic.js';
|
|
|
26
29
|
import { stripFlags } from '../../core/flags.js';
|
|
27
30
|
import { stripDevflowTeammateModeFromJson } from '../../core/teammate-mode-cleanup.js';
|
|
28
31
|
import { getPackageRoot, isContainedIn } from '../../core/paths.js';
|
|
32
|
+
/**
|
|
33
|
+
* The plugins the manifest records as installed, as registry definitions.
|
|
34
|
+
*
|
|
35
|
+
* Falls back to the whole registry when there is no readable manifest, or when
|
|
36
|
+
* it names nothing this registry still has: that is the pre-manifest and the
|
|
37
|
+
* corrupt-manifest case, and retaining too much is the safe direction for a
|
|
38
|
+
* removal. Names the manifest carries that the registry has since dropped are
|
|
39
|
+
* skipped rather than invented — a definition is what the retained-set
|
|
40
|
+
* arithmetic needs, and there is none for a deleted plugin.
|
|
41
|
+
*/
|
|
42
|
+
export async function resolveInstalledPlugins(devflowDir) {
|
|
43
|
+
const manifest = await readManifest(devflowDir).catch(() => null);
|
|
44
|
+
const names = new Set(manifest?.plugins ?? []);
|
|
45
|
+
if (names.size === 0)
|
|
46
|
+
return DEVFLOW_PLUGINS;
|
|
47
|
+
const resolved = DEVFLOW_PLUGINS.filter(plugin => names.has(plugin.name));
|
|
48
|
+
return resolved.length === 0 ? DEVFLOW_PLUGINS : resolved;
|
|
49
|
+
}
|
|
29
50
|
/**
|
|
30
51
|
* Compute which assets should be removed during selective plugin uninstall.
|
|
31
52
|
* Skills and agents shared by remaining plugins are retained.
|
|
32
53
|
* Rules shared by remaining plugins are also retained.
|
|
54
|
+
*
|
|
55
|
+
* D-RETAIN-FROM-MANIFEST: `installedPlugins` is what the MANIFEST records as
|
|
56
|
+
* installed, not the whole registry. Once skills are plugin-scoped the two stop
|
|
57
|
+
* agreeing, and taking the registry retains assets on behalf of plugins the user
|
|
58
|
+
* never installed — so `devflow uninstall --plugin=X` keeps X's skills alive
|
|
59
|
+
* because some unselected plugin also declares them, and the user is left with
|
|
60
|
+
* exactly the files they asked to remove. Skills are retained across the CLOSURE
|
|
61
|
+
* (`skills ∪ requires`) of the remaining plugins, for the same reason the
|
|
62
|
+
* install set is a closure: a skill another installed plugin merely requires is
|
|
63
|
+
* still a skill it needs.
|
|
64
|
+
*
|
|
65
|
+
* @param installedPlugins - The plugins the manifest records. Callers pass the
|
|
66
|
+
* registry only when there is no manifest to read, and the dry-run and the
|
|
67
|
+
* real removal must always be given the SAME list or they describe different
|
|
68
|
+
* outcomes.
|
|
33
69
|
*/
|
|
34
|
-
export function computeAssetsToRemove(selectedPlugins,
|
|
70
|
+
export function computeAssetsToRemove(selectedPlugins, installedPlugins) {
|
|
35
71
|
const selectedNames = new Set(selectedPlugins.map(p => p.name));
|
|
36
|
-
const remainingPlugins =
|
|
37
|
-
const retainedSkills =
|
|
72
|
+
const remainingPlugins = installedPlugins.filter(p => !selectedNames.has(p.name));
|
|
73
|
+
const retainedSkills = skillsOf(remainingPlugins);
|
|
38
74
|
const retainedAgents = new Set();
|
|
39
75
|
const retainedRules = new Set();
|
|
40
76
|
for (const rp of remainingPlugins) {
|
|
41
|
-
for (const s of rp.skills)
|
|
42
|
-
retainedSkills.add(s);
|
|
43
77
|
for (const a of rp.agents)
|
|
44
78
|
retainedAgents.add(a);
|
|
45
79
|
for (const r of rp.rules)
|
|
@@ -49,11 +83,11 @@ export function computeAssetsToRemove(selectedPlugins, allPlugins) {
|
|
|
49
83
|
const agents = [];
|
|
50
84
|
const commands = [];
|
|
51
85
|
const rules = [];
|
|
86
|
+
for (const skill of skillsOf(selectedPlugins)) {
|
|
87
|
+
if (!retainedSkills.has(skill))
|
|
88
|
+
skills.push(skill);
|
|
89
|
+
}
|
|
52
90
|
for (const plugin of selectedPlugins) {
|
|
53
|
-
for (const skill of plugin.skills) {
|
|
54
|
-
if (!retainedSkills.has(skill))
|
|
55
|
-
skills.push(skill);
|
|
56
|
-
}
|
|
57
91
|
for (const agent of plugin.agents) {
|
|
58
92
|
if (!retainedAgents.has(agent))
|
|
59
93
|
agents.push(agent);
|
|
@@ -183,21 +217,69 @@ export function resolveDevflowDirCleanup(opts) {
|
|
|
183
217
|
return 'prompt';
|
|
184
218
|
}
|
|
185
219
|
/**
|
|
186
|
-
*
|
|
187
|
-
* full cleanup of the directory.
|
|
220
|
+
* Single source of truth for user-authored content under `devflowDir`.
|
|
188
221
|
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* devflowDir/hud.json — HUD enable/disable preference and display config
|
|
222
|
+
* The counterpart to `installArtifactPaths`: together the two lists describe
|
|
223
|
+
* every Devflow-created entry in `~/.devflow`, and they never overlap (@D8).
|
|
224
|
+
* Membership here means "survives every path but a confirmed full-dir rm";
|
|
225
|
+
* membership there means "removed on decline, cancel, non-interactive and
|
|
226
|
+
* --keep-docs alike".
|
|
195
227
|
*
|
|
196
228
|
* NOT listed here: agent-models.json — reclassified as an INSTALL ARTIFACT (AC-P1-F4).
|
|
197
229
|
* Stale per-agent model overrides silently re-apply to renamed/deleted agents on reinstall,
|
|
198
230
|
* so it must be cleaned up by removeDevFlowInstallArtifacts, not preserved behind a confirm gate.
|
|
199
231
|
*
|
|
200
|
-
*
|
|
232
|
+
* @param devflowDir - Absolute path to ~/.devflow (labels quote the shadow dirs).
|
|
233
|
+
*/
|
|
234
|
+
export function userContentPaths(devflowDir) {
|
|
235
|
+
return [
|
|
236
|
+
// Skill shadow overrides (~/.devflow/skills/{name}/)
|
|
237
|
+
{ relPath: 'skills', isDir: true, label: `skill shadows (${path.join(devflowDir, 'skills')})` },
|
|
238
|
+
// Rule shadow overrides (~/.devflow/rules/{name}.md)
|
|
239
|
+
{ relPath: 'rules', isDir: true, label: `rule shadows (${path.join(devflowDir, 'rules')})` },
|
|
240
|
+
// preference-profile.md — user-curated decision-preference profile
|
|
241
|
+
{ relPath: 'preference-profile.md', label: 'preference-profile.md' },
|
|
242
|
+
// tracker.md — the inferred, hand-editable issue-tracker conventions file.
|
|
243
|
+
//
|
|
244
|
+
// USER CONTENT (OD-15), classified the same way as preference-profile.md above
|
|
245
|
+
// rather than as an install artifact like agent-models.json, because it is
|
|
246
|
+
// inferred ONCE per machine and then hand-editable: absence is the trigger that
|
|
247
|
+
// re-runs inference, so deleting it on every decline/cancel/--keep-docs path
|
|
248
|
+
// would silently discard work the user may have corrected by hand.
|
|
249
|
+
//
|
|
250
|
+
// REVERSAL CONDITION, recorded: this classification is CONDITIONAL on the
|
|
251
|
+
// provider-mismatch guard shipping. agent-models.json was reclassified to an
|
|
252
|
+
// artifact precisely because stale overrides re-apply *silently*; "silently" is
|
|
253
|
+
// the load-bearing word. A stale tracker.md whose frontmatter provider
|
|
254
|
+
// disagrees with the resolved provider produces
|
|
255
|
+
// `TRACEABILITY: DEGRADED (tracker configuration mismatch (conventions file))`
|
|
256
|
+
// and no tracker call — that is what removes the silence, and the reason names
|
|
257
|
+
// THIS file rather than the per-repo override so the user is told which of the
|
|
258
|
+
// two to edit. If that guard is ever dropped,
|
|
259
|
+
// reclassify tracker.md to an install artifact IN THE SAME CHANGE, otherwise a
|
|
260
|
+
// silently-authoritative stale file survives uninstall.
|
|
261
|
+
{ relPath: TRACKER_CONVENTIONS_FILE, label: `${TRACKER_CONVENTIONS_FILE} (issue tracker conventions)` },
|
|
262
|
+
// tracker.md.{provider}.bak — what renameStaleTrackerConventions leaves behind
|
|
263
|
+
// on a provider change. Same inferred content as tracker.md (the user's site
|
|
264
|
+
// and project key), so the same classification: named by the confirm prompt,
|
|
265
|
+
// kept by every artifacts-only pass. The names come from the registry, so a
|
|
266
|
+
// fourth provider is covered the day it lands.
|
|
267
|
+
...TRACKER_CONVENTIONS_BACKUP_NAMES.map(name => ({
|
|
268
|
+
relPath: name,
|
|
269
|
+
label: `${name} (previous issue tracker conventions)`,
|
|
270
|
+
})),
|
|
271
|
+
// learning.json — global learning agent tuning config
|
|
272
|
+
{ relPath: 'learning.json', label: 'learning.json' },
|
|
273
|
+
// hud.json — user HUD enable/disable preference and display config
|
|
274
|
+
{ relPath: 'hud.json', label: 'hud.json (HUD configuration)' },
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Enumerate user-authored content in devflowDir that would be deleted by a
|
|
279
|
+
* full cleanup of the directory.
|
|
280
|
+
*
|
|
281
|
+
* Walks `userContentPaths` and returns the label of every entry that exists on
|
|
282
|
+
* disk — a directory counts only when it is non-empty. Empty array means nothing
|
|
201
283
|
* user-authored is present in the directory.
|
|
202
284
|
*
|
|
203
285
|
* Pure I/O — no side effects, no output, fully testable.
|
|
@@ -207,41 +289,54 @@ export function resolveDevflowDirCleanup(opts) {
|
|
|
207
289
|
*/
|
|
208
290
|
export async function enumerateUserDevFlowContent(devflowDir) {
|
|
209
291
|
const items = [];
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
292
|
+
for (const entry of userContentPaths(devflowDir)) {
|
|
293
|
+
const fullPath = path.join(devflowDir, entry.relPath);
|
|
294
|
+
try {
|
|
295
|
+
if (entry.isDir) {
|
|
296
|
+
const children = await fs.readdir(fullPath);
|
|
297
|
+
if (children.length === 0)
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
await fs.access(fullPath);
|
|
302
|
+
}
|
|
303
|
+
items.push(entry.label);
|
|
215
304
|
}
|
|
305
|
+
catch { /* absent or unreadable */ }
|
|
216
306
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
307
|
+
return items;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Expand `installArtifactPaths` against real disk.
|
|
311
|
+
*
|
|
312
|
+
* Exact entries pass through untouched; a prefix entry becomes one entry per
|
|
313
|
+
* matching direct child of `devflowDir`. This is the ONE place a prefix turns
|
|
314
|
+
* into paths, so the removal loop and the dry-run preview cannot disagree about
|
|
315
|
+
* what a prefix covers. An unreadable or absent `devflowDir` yields the exact
|
|
316
|
+
* entries alone — absent is the ordinary case here, never an error.
|
|
317
|
+
*/
|
|
318
|
+
export async function resolveInstallArtifactPaths(devflowDir) {
|
|
319
|
+
const resolved = [];
|
|
320
|
+
let children = null;
|
|
321
|
+
for (const entry of installArtifactPaths(devflowDir)) {
|
|
322
|
+
if (entry.isPrefix !== true) {
|
|
323
|
+
resolved.push({ relPath: entry.relPath, isDir: entry.isDir });
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (children === null) {
|
|
327
|
+
try {
|
|
328
|
+
children = await fs.readdir(devflowDir);
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
children = [];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
for (const child of children) {
|
|
335
|
+
if (child.startsWith(entry.relPath))
|
|
336
|
+
resolved.push({ relPath: child, isDir: entry.isDir });
|
|
223
337
|
}
|
|
224
338
|
}
|
|
225
|
-
|
|
226
|
-
// preference-profile.md — user-curated decision-preference profile
|
|
227
|
-
try {
|
|
228
|
-
await fs.access(path.join(devflowDir, 'preference-profile.md'));
|
|
229
|
-
items.push('preference-profile.md');
|
|
230
|
-
}
|
|
231
|
-
catch { /* absent */ }
|
|
232
|
-
// learning.json — global learning agent tuning config
|
|
233
|
-
try {
|
|
234
|
-
await fs.access(path.join(devflowDir, 'learning.json'));
|
|
235
|
-
items.push('learning.json');
|
|
236
|
-
}
|
|
237
|
-
catch { /* absent */ }
|
|
238
|
-
// hud.json — user HUD enable/disable preference and display config
|
|
239
|
-
try {
|
|
240
|
-
await fs.access(path.join(devflowDir, 'hud.json'));
|
|
241
|
-
items.push('hud.json (HUD configuration)');
|
|
242
|
-
}
|
|
243
|
-
catch { /* absent */ }
|
|
244
|
-
return items;
|
|
339
|
+
return resolved;
|
|
245
340
|
}
|
|
246
341
|
/**
|
|
247
342
|
* Single source of truth for Devflow-owned install artifacts under `devflowDir`.
|
|
@@ -252,9 +347,18 @@ export async function enumerateUserDevFlowContent(devflowDir) {
|
|
|
252
347
|
* lets callers (dry-run display, tests) enumerate the artifact set without
|
|
253
348
|
* duplicating the list.
|
|
254
349
|
*
|
|
255
|
-
* @D8 Nothing returned here may overlap with the
|
|
256
|
-
*
|
|
257
|
-
*
|
|
350
|
+
* @D8 Nothing returned here may overlap with `userContentPaths` — the two lists
|
|
351
|
+
* are the whole of what Devflow puts in `~/.devflow`, and a name in both is
|
|
352
|
+
* deleted whatever the user answers to the full-wipe prompt. Checked two ways:
|
|
353
|
+
* mechanically, by intersecting the `relPath` sets of the two functions and
|
|
354
|
+
* checking no user path falls UNDER a prefix entry (so an entry added to either
|
|
355
|
+
* list is covered the day it lands), and behaviourally, by test 9f — every
|
|
356
|
+
* enumerated user item survives an artifact-only removal.
|
|
357
|
+
*
|
|
358
|
+
* Entries marked `isPrefix` name a FAMILY of per-invocation basenames rather
|
|
359
|
+
* than one path; `resolveInstallArtifactPaths` is what turns them into paths.
|
|
360
|
+
* Every consumer goes through that resolver, so a prefix entry is never treated
|
|
361
|
+
* as a literal filename.
|
|
258
362
|
*
|
|
259
363
|
* @param devflowDir - Absolute path to ~/.devflow (used to resolve cache dir).
|
|
260
364
|
*/
|
|
@@ -274,6 +378,20 @@ export function installArtifactPaths(devflowDir) {
|
|
|
274
378
|
{ relPath: 'proxy-routing.json' },
|
|
275
379
|
{ relPath: 'proxy.pid' },
|
|
276
380
|
{ relPath: '.proxy-spawn.lock', isDir: true },
|
|
381
|
+
// tracker runtime artifacts — the Tracker agent's atomic claim file, its
|
|
382
|
+
// inference attempt counter, and the provider presence sentinel the
|
|
383
|
+
// SessionStart hook stats. All three are machine state with no user-authored
|
|
384
|
+
// content, so they go on this list; `tracker.md` beside them and the
|
|
385
|
+
// `tracker.md.{provider}.bak` a provider change leaves are USER CONTENT
|
|
386
|
+
// (OD-15) and are deliberately NOT here (@D8: the two lists stay disjoint).
|
|
387
|
+
{ relPath: TRACKER_CLAIM_FILE },
|
|
388
|
+
{ relPath: TRACKER_ATTEMPTS_FILE },
|
|
389
|
+
{ relPath: TRACKER_ENABLED_FILE },
|
|
390
|
+
// The agent's scrubbed staging file, one per invocation under a mktemp name
|
|
391
|
+
// it removes from a trap — a SIGKILL outruns the trap and leaves it behind.
|
|
392
|
+
// A prefix, because the names exist only on disk. Content is a scrubbed copy
|
|
393
|
+
// that was never placed, so it is machine state like the three above.
|
|
394
|
+
{ relPath: TRACKER_STAGED_PREFIX, isPrefix: true },
|
|
277
395
|
// per-project hook logs (logs/{project-slug}/) AND global logs — remove the
|
|
278
396
|
// whole logs/ tree; covers proxy.log, debug logs, and any project-slug dirs.
|
|
279
397
|
{ relPath: 'logs', isDir: true },
|
|
@@ -295,7 +413,8 @@ export function installArtifactPaths(devflowDir) {
|
|
|
295
413
|
* @D8 Nothing enumerated by enumerateUserDevFlowContent may appear in this list.
|
|
296
414
|
* This function runs on the decline, cancel, non-interactive AND --keep-docs paths,
|
|
297
415
|
* so an entry here is deleted even when the user answers "no" to the full wipe.
|
|
298
|
-
* User-authored state (skill/rule shadows,
|
|
416
|
+
* User-authored state (everything in `userContentPaths`: skill/rule shadows,
|
|
417
|
+
* preference-profile.md, tracker.md and its provider backups, learning.json,
|
|
299
418
|
* hud.json) is removed only by the confirmed full-dir rm.
|
|
300
419
|
* agent-models.json is an INSTALL ARTIFACT (stale per-agent overrides silently
|
|
301
420
|
* re-apply to renamed/deleted agents on reinstall — AC-P1-F4) and therefore
|
|
@@ -327,8 +446,10 @@ export async function removeDevFlowInstallArtifacts(devflowDir, verbose) {
|
|
|
327
446
|
}
|
|
328
447
|
}
|
|
329
448
|
catch { /* proxy.pid absent or unreadable — non-fatal */ }
|
|
330
|
-
// All install artifacts removed non-fatally (avoids PF-009).
|
|
331
|
-
|
|
449
|
+
// All install artifacts removed non-fatally (avoids PF-009). Resolved against
|
|
450
|
+
// disk first, so a per-run staging basename is a real path by the time the
|
|
451
|
+
// containment guard below sees it.
|
|
452
|
+
for (const artifact of await resolveInstallArtifactPaths(devflowDir)) {
|
|
332
453
|
const fullPath = path.join(devflowDir, artifact.relPath);
|
|
333
454
|
// Containment invariant: every artifact must resolve to a path STRICTLY inside
|
|
334
455
|
// devflowDir. A derived relPath that ever collapsed to '' or '..' would turn the
|
|
@@ -456,7 +577,7 @@ export async function enumerateDryRunExtras(claudeDir, devflowDir) {
|
|
|
456
577
|
// Guard with fs.access so files that never existed don't pollute the preview.
|
|
457
578
|
// (F7: previously pushed unconditionally, inflating the dry-run list with
|
|
458
579
|
// paths that were never on disk.)
|
|
459
|
-
for (const artifact of
|
|
580
|
+
for (const artifact of await resolveInstallArtifactPaths(devflowDir)) {
|
|
460
581
|
const fullPath = path.join(devflowDir, artifact.relPath);
|
|
461
582
|
try {
|
|
462
583
|
await fs.access(fullPath);
|
|
@@ -476,13 +597,18 @@ export async function enumerateDryRunExtras(claudeDir, devflowDir) {
|
|
|
476
597
|
* @param opts.scopesToUninstall - Scopes detected in the setup phase.
|
|
477
598
|
* @param opts.isSelectiveUninstall - true when --plugin was given.
|
|
478
599
|
* @param opts.selectedPlugins - The plugin subset for selective mode.
|
|
600
|
+
* @param opts.installedPlugins - What the manifest records as installed. The
|
|
601
|
+
* dry-run and the real removal MUST receive the same list, or the preview
|
|
602
|
+
* describes an outcome the removal does not produce.
|
|
479
603
|
*/
|
|
480
604
|
export async function runDryRunPhase(opts) {
|
|
481
|
-
const { scopesToUninstall, isSelectiveUninstall, selectedPlugins } = opts;
|
|
605
|
+
const { scopesToUninstall, isSelectiveUninstall, selectedPlugins, installedPlugins } = opts;
|
|
482
606
|
p.log.info(`Scope(s): ${[...scopesToUninstall].join(', ')} (dry-run shows all detected scopes)`);
|
|
483
607
|
if (isSelectiveUninstall) {
|
|
484
|
-
// Selective:
|
|
485
|
-
|
|
608
|
+
// Selective: computed against the INSTALLED list, the same argument the real
|
|
609
|
+
// removal is given below — a preview computed from a different list is a
|
|
610
|
+
// preview of a different uninstall.
|
|
611
|
+
const assets = computeAssetsToRemove(selectedPlugins, installedPlugins);
|
|
486
612
|
const plan = formatDryRunPlan(assets);
|
|
487
613
|
for (const line of plan.split('\n')) {
|
|
488
614
|
p.log.info(line);
|
|
@@ -527,6 +653,7 @@ export async function runDryRunPhase(opts) {
|
|
|
527
653
|
*/
|
|
528
654
|
export async function runSelectivePhaseForScope(opts) {
|
|
529
655
|
const { claudeDir, devflowDir, selectedPlugins, verbose } = opts;
|
|
656
|
+
const installedPlugins = opts.installedPlugins ?? DEVFLOW_PLUGINS;
|
|
530
657
|
// Revert GPT agent frontmatter BEFORE removing agent files — strips GPT model
|
|
531
658
|
// lines from installed agent frontmatter while the files are still present.
|
|
532
659
|
// Non-fatal: tolerate missing agents dir or revert errors.
|
|
@@ -543,7 +670,7 @@ export async function runSelectivePhaseForScope(opts) {
|
|
|
543
670
|
}
|
|
544
671
|
catch { /* agents dir absent or revert failed — non-fatal */ }
|
|
545
672
|
}
|
|
546
|
-
await removeSelectedPlugins(claudeDir, selectedPlugins, verbose);
|
|
673
|
+
await removeSelectedPlugins(claudeDir, selectedPlugins, verbose, installedPlugins);
|
|
547
674
|
// Clean up ambient hook if ambient plugin is being removed
|
|
548
675
|
if (selectedPlugins.some(sp => sp.name === 'devflow-ambient')) {
|
|
549
676
|
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
@@ -972,7 +1099,20 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
972
1099
|
}
|
|
973
1100
|
// === DRY RUN: show plan and exit ===
|
|
974
1101
|
if (dryRun) {
|
|
975
|
-
|
|
1102
|
+
// One resolution, handed to the dry-run and (below) to the real removal,
|
|
1103
|
+
// so the preview and the outcome are computed from the same list.
|
|
1104
|
+
let dryRunInstalled = DEVFLOW_PLUGINS;
|
|
1105
|
+
try {
|
|
1106
|
+
const paths = await getInstallationPaths(scopesToUninstall[0]);
|
|
1107
|
+
dryRunInstalled = await resolveInstalledPlugins(paths.devflowDir);
|
|
1108
|
+
}
|
|
1109
|
+
catch { /* scope path resolution failed — fall back to the registry */ }
|
|
1110
|
+
await runDryRunPhase({
|
|
1111
|
+
scopesToUninstall,
|
|
1112
|
+
isSelectiveUninstall,
|
|
1113
|
+
selectedPlugins,
|
|
1114
|
+
installedPlugins: dryRunInstalled,
|
|
1115
|
+
});
|
|
976
1116
|
p.outro(color.dim('No changes made (dry run)'));
|
|
977
1117
|
return;
|
|
978
1118
|
}
|
|
@@ -1021,7 +1161,13 @@ export const uninstallCommand = new Command('uninstall')
|
|
|
1021
1161
|
continue;
|
|
1022
1162
|
}
|
|
1023
1163
|
if (isSelectiveUninstall) {
|
|
1024
|
-
await runSelectivePhaseForScope({
|
|
1164
|
+
await runSelectivePhaseForScope({
|
|
1165
|
+
claudeDir,
|
|
1166
|
+
devflowDir,
|
|
1167
|
+
selectedPlugins,
|
|
1168
|
+
verbose,
|
|
1169
|
+
installedPlugins: await resolveInstalledPlugins(devflowDir),
|
|
1170
|
+
});
|
|
1025
1171
|
}
|
|
1026
1172
|
else {
|
|
1027
1173
|
await runFullPhaseForScope({ scope, claudeDir, devflowDir, devflowScriptsDir, verbose, keepDocs: !!options.keepDocs, isTTY: !!process.stdin.isTTY });
|
|
@@ -1161,8 +1307,8 @@ export async function sweepDevflowNamespaces(claudeDir, verbose) {
|
|
|
1161
1307
|
* registry (retired agents/commands survive indefinitely without the sweep).
|
|
1162
1308
|
* For skills: only remove skills that are NOT used by any remaining plugin.
|
|
1163
1309
|
*/
|
|
1164
|
-
export async function removeSelectedPlugins(claudeDir, plugins, verbose) {
|
|
1165
|
-
const { skills, agents, commands, rules } = computeAssetsToRemove(plugins,
|
|
1310
|
+
export async function removeSelectedPlugins(claudeDir, plugins, verbose, installedPlugins = DEVFLOW_PLUGINS) {
|
|
1311
|
+
const { skills, agents, commands, rules } = computeAssetsToRemove(plugins, installedPlugins);
|
|
1166
1312
|
const commandsDir = path.join(claudeDir, 'commands', 'devflow');
|
|
1167
1313
|
for (const cmd of commands) {
|
|
1168
1314
|
const cmdFileName = mdFileName(cmd.replace(/^\//, ''));
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,7 @@ import { safeDeleteCommand } from './cli/commands/safe-delete.js';
|
|
|
19
19
|
import { proxyCommand } from './cli/commands/proxy.js';
|
|
20
20
|
import { agentsCommand } from './cli/commands/agents.js';
|
|
21
21
|
import { complianceCommand } from './cli/commands/compliance.js';
|
|
22
|
+
import { trackerCommand } from './cli/commands/tracker.js';
|
|
22
23
|
const __filename = fileURLToPath(import.meta.url);
|
|
23
24
|
const __dirname = dirname(__filename);
|
|
24
25
|
// Read version from package.json
|
|
@@ -47,6 +48,7 @@ program.addCommand(safeDeleteCommand);
|
|
|
47
48
|
program.addCommand(proxyCommand);
|
|
48
49
|
program.addCommand(agentsCommand);
|
|
49
50
|
program.addCommand(complianceCommand);
|
|
51
|
+
program.addCommand(trackerCommand);
|
|
50
52
|
// Handle no command (bare `devflow`) or unknown subcommand.
|
|
51
53
|
// When Commander sees an unrecognised first argument it does not route to any
|
|
52
54
|
// registered subcommand; instead the root action fires with that argument
|
|
@@ -17,17 +17,35 @@ Run a proactive bug analysis on the current branch by combining static analysis
|
|
|
17
17
|
|
|
18
18
|
### Phase 1: Pre-flight
|
|
19
19
|
|
|
20
|
-
**Produces:** BRANCH_INFO, PR_DESCRIPTION,
|
|
20
|
+
**Produces:** BRANCH_INFO, PR_DESCRIPTION, EVIDENCE_POLICY, ISSUE_REQUIRED, APPLY_CONVENTIONS, REQUIRE_NON_AUTHOR_APPROVAL
|
|
21
21
|
|
|
22
|
-
**Resolve
|
|
23
|
-
|
|
22
|
+
**Resolve the evidence policy once per run**, from the repository root, before any step reads the values:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/resolve-evidence-policy.cjs" 2>/dev/null; echo "exit=$?"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `EVIDENCE_POLICY=<required|standard> SOURCE=<file|worktree|default|invalid|error> REF=<branch|none>[ WARN=<remote-unavailable|invalid-file|raised-by-compliance|pr-changes-policy>[,…]] ISSUE_REQUIRED=<true|false> APPLY_CONVENTIONS=<true|false> REQUIRE_NON_AUTHOR_APPROVAL=<true|false>` — these fields, in this order, nothing else, where `<branch>` is a branch name such as `main`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `EVIDENCE_POLICY=required SOURCE=error REF=none ISSUE_REQUIRED=true APPLY_CONVENTIONS=true REQUIRE_NON_AUTHOR_APPROVAL=true` instead.
|
|
29
|
+
|
|
30
|
+
Set `EVIDENCE_POLICY`, `ISSUE_REQUIRED`, `APPLY_CONVENTIONS` and `REQUIRE_NON_AUTHOR_APPROVAL` from the accepted line. Pass agents only the three mechanism inputs, never `EVIDENCE_POLICY`. Report `Evidence policy: {EVIDENCE_POLICY} (source: {SOURCE})`, plus any `WARN` tokens as advisory, once in the final report.
|
|
31
|
+
|
|
32
|
+
Render the test-plan block from `/implement`'s evidence file, and from nothing else:
|
|
33
|
+
1. `branch_slug` is `git branch --show-current` with every `/` replaced by `-`.
|
|
34
|
+
2. Only when `branch_slug` matches `^[A-Za-z0-9._-]{1,200}$` and the file exists, run (the path double-quoted):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/verify-evidence.cjs" render --plan ".devflow/docs/evidence-{branch_slug}.md"; echo "exit=$?"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. On `exit=0`, `PR_TEST_PLAN_BLOCK` is its stdout byte for byte without that `exit=` line; in every other case it is `(none)`. Nothing here waits on it: the Git agent pastes it only behind its own check, and only into a PR it creates.
|
|
24
41
|
|
|
25
42
|
Spawn Git agent:
|
|
26
43
|
|
|
27
44
|
```
|
|
28
45
|
Agent(subagent_type="Git", run_in_background=false):
|
|
29
46
|
"OPERATION: ensure-pr-ready
|
|
30
|
-
|
|
47
|
+
PR_TEST_PLAN_BLOCK: {PR_TEST_PLAN_BLOCK verbatim, or (none)}
|
|
48
|
+
APPLY_CONVENTIONS: {APPLY_CONVENTIONS}
|
|
31
49
|
Validate branch, commit if needed, push, create PR if needed.
|
|
32
50
|
Return: branch, base_branch, branch-slug, PR#"
|
|
33
51
|
```
|
|
@@ -37,6 +37,20 @@ Run a comprehensive code review of the current branch by spawning parallel revie
|
|
|
37
37
|
**Resolve `COMPLIANCE_SKILL_INSTALLED` once per run:** Check whether `~/.claude/skills/devflow:compliance/SKILL.md` exists (one file-existence check, read-only, silent). Set `COMPLIANCE_SKILL_INSTALLED = true` if the file exists, `false` otherwise.
|
|
38
38
|
Reuse this result for every worktree and every downstream phase.
|
|
39
39
|
|
|
40
|
+
#### Step 0b-ii: Resolve the evidence policy
|
|
41
|
+
|
|
42
|
+
**Produces:** EVIDENCE_POLICY, ISSUE_REQUIRED, APPLY_CONVENTIONS, REQUIRE_NON_AUTHOR_APPROVAL
|
|
43
|
+
|
|
44
|
+
**Resolve the evidence policy once per run**, from the repository root, before any step reads the values:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/resolve-evidence-policy.cjs" 2>/dev/null; echo "exit=$?"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `EVIDENCE_POLICY=<required|standard> SOURCE=<file|worktree|default|invalid|error> REF=<branch|none>[ WARN=<remote-unavailable|invalid-file|raised-by-compliance|pr-changes-policy>[,…]] ISSUE_REQUIRED=<true|false> APPLY_CONVENTIONS=<true|false> REQUIRE_NON_AUTHOR_APPROVAL=<true|false>` — these fields, in this order, nothing else, where `<branch>` is a branch name such as `main`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `EVIDENCE_POLICY=required SOURCE=error REF=none ISSUE_REQUIRED=true APPLY_CONVENTIONS=true REQUIRE_NON_AUTHOR_APPROVAL=true` instead.
|
|
51
|
+
|
|
52
|
+
Set `EVIDENCE_POLICY`, `ISSUE_REQUIRED`, `APPLY_CONVENTIONS` and `REQUIRE_NON_AUTHOR_APPROVAL` from the accepted line. Pass agents only the three mechanism inputs, never `EVIDENCE_POLICY`. Report `Evidence policy: {EVIDENCE_POLICY} (source: {SOURCE})`, plus any `WARN` tokens as advisory, once in the final report.
|
|
53
|
+
|
|
40
54
|
#### Step 0c: Per-Worktree Pre-Flight (Git Agent)
|
|
41
55
|
|
|
42
56
|
**Produces:** BRANCH_INFO, PR_INFO, PR_DESCRIPTION, PR_DESCRIPTION_GUIDANCE
|
|
@@ -48,6 +62,16 @@ Discover PR description guidance from plan artifact (per worktree):
|
|
|
48
62
|
3. Read the most recent file, extract `## PR Description Guidance` section
|
|
49
63
|
4. If no plan files exist or section not found, set `PR_DESCRIPTION_GUIDANCE` to `(none)`
|
|
50
64
|
|
|
65
|
+
Render the test-plan block (per worktree) from `/implement`'s evidence file, and from nothing else:
|
|
66
|
+
1. `branch_slug` is `git -C "{worktree}" branch --show-current` with every `/` replaced by `-`.
|
|
67
|
+
2. Only when `branch_slug` matches `^[A-Za-z0-9._-]{1,200}$` and the file exists, run (the path double-quoted):
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node "${DEVFLOW_DIR:-$HOME/.devflow}/scripts/verify-evidence.cjs" render --plan "{worktree}/.devflow/docs/evidence-{branch_slug}.md"; echo "exit=$?"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
3. On `exit=0`, `PR_TEST_PLAN_BLOCK` is its stdout byte for byte without that `exit=` line; in every other case it is `(none)`. Nothing here waits on it: the Git agent pastes it only behind its own check, and only into a PR it creates.
|
|
74
|
+
|
|
51
75
|
For each reviewable worktree, spawn Git agent:
|
|
52
76
|
|
|
53
77
|
```
|
|
@@ -55,7 +79,8 @@ Agent(subagent_type="Git", run_in_background=false):
|
|
|
55
79
|
"OPERATION: ensure-pr-ready
|
|
56
80
|
WORKTREE_PATH: {worktree_path} (omit if cwd)
|
|
57
81
|
PR_DESCRIPTION_GUIDANCE: {pr_description_guidance}
|
|
58
|
-
|
|
82
|
+
PR_TEST_PLAN_BLOCK: {PR_TEST_PLAN_BLOCK verbatim, or (none)}
|
|
83
|
+
APPLY_CONVENTIONS: {APPLY_CONVENTIONS}
|
|
59
84
|
Validate branch, commit if needed, push, create PR if needed.
|
|
60
85
|
Return: branch, base_branch, branch-slug, PR#"
|
|
61
86
|
```
|
|
@@ -133,13 +158,15 @@ MAX_REVIEW_CYCLES = 10
|
|
|
133
158
|
#### Step 0f: Resolve Publication Mode (Per Worktree)
|
|
134
159
|
|
|
135
160
|
**Produces:** REVIEW_PUBLICATION
|
|
136
|
-
**Requires:** WORKTREES
|
|
161
|
+
**Requires:** WORKTREES, EVIDENCE_POLICY
|
|
137
162
|
|
|
138
163
|
For each reviewable worktree, call:
|
|
139
164
|
|
|
140
165
|
**Resolve `REVIEW_PUBLICATION` per worktree:** Read the current worktree's `.devflow/config.json` (a single, direct file read — multi-worktree repos may have different publication settings per worktree root). If the file exists and `reviewPublication` is one of `auto`, `full`, or `off`, set `REVIEW_PUBLICATION` to that value; otherwise set `REVIEW_PUBLICATION = "auto"`.
|
|
141
166
|
|
|
142
|
-
|
|
167
|
+
**Evidence stub:** only when `EVIDENCE_POLICY` is `required`, a resolved `off` becomes `stub`, so a counts-only record still reaches the PR. `stub` is never a config value: a configured `stub` is unrecognised and resolves to `auto` like any other.
|
|
168
|
+
|
|
169
|
+
Note: `auto` is NOT fail-open — under `auto`, the Git agent probes the repository visibility and treats any error or unrecognised value as PUBLIC (mode STUB). What each value does is decided by the Git agent's publication gate (`references/publication-gate.md` step 2); this partial only resolves the value.
|
|
143
170
|
|
|
144
171
|
### Phase 1: Analyze Changed Files
|
|
145
172
|
|
|
@@ -165,6 +192,8 @@ Per worktree, detect file types in diff using `DIFF_RANGE` to determine conditio
|
|
|
165
192
|
|
|
166
193
|
If `COMPLIANCE_SKILL_INSTALLED` AND the diff touches regulated surface (data models, auth flows, logging/observability, payments, IaC, retention): add `compliance` to REVIEW_FOCUS_LIST for this worktree.
|
|
167
194
|
|
|
195
|
+
**Language focus presence gate.** The eight language focuses — `typescript`, `react`, `accessibility`, `ui-design`, `go`, `java`, `python`, `rust` — ship with optional plugins, so their pattern skills are installed only when the user selected that plugin. Gate them exactly as `compliance` is gated: for each language focus the table above would add, check whether `~/.claude/skills/devflow:{focus}/SKILL.md` exists (one file-existence check per candidate focus, read-only, silent). If it does not exist, do NOT add that focus to `REVIEW_FOCUS_LIST` and do NOT spawn a Review agent for it — the file-type condition alone never spawns a language focus. The eight core focuses are unconditional and are never presence-gated.
|
|
196
|
+
|
|
168
197
|
### Phase 1b: Load Decisions Index
|
|
169
198
|
|
|
170
199
|
**Produces:** DECISIONS_CONTEXT, FEATURE_KNOWLEDGE, COMPLIANCE_SKILL_INSTALLED (carried from Step 0b)
|
|
@@ -248,14 +277,14 @@ Spawn Review agents **in a single message**. Always run 8 core reviews; conditio
|
|
|
248
277
|
| regression | ✓ | devflow:regression |
|
|
249
278
|
| testing | ✓ | devflow:testing |
|
|
250
279
|
| reliability | ✓ | devflow:reliability |
|
|
251
|
-
| typescript |
|
|
252
|
-
| react |
|
|
253
|
-
| accessibility |
|
|
254
|
-
| ui-design |
|
|
255
|
-
| go |
|
|
256
|
-
| java |
|
|
257
|
-
| python |
|
|
258
|
-
| rust |
|
|
280
|
+
| typescript | presence-gated | devflow:typescript |
|
|
281
|
+
| react | presence-gated | devflow:react |
|
|
282
|
+
| accessibility | presence-gated | devflow:accessibility |
|
|
283
|
+
| ui-design | presence-gated | devflow:ui-design |
|
|
284
|
+
| go | presence-gated | devflow:go |
|
|
285
|
+
| java | presence-gated | devflow:java |
|
|
286
|
+
| python | presence-gated | devflow:python |
|
|
287
|
+
| rust | presence-gated | devflow:rust |
|
|
259
288
|
| database | conditional | devflow:database |
|
|
260
289
|
| dependencies | conditional | devflow:dependencies |
|
|
261
290
|
| documentation | conditional | devflow:documentation |
|
|
@@ -309,7 +338,7 @@ Output: {worktree_path}/.devflow/docs/reviews/{branch-slug}/{timestamp}/review-s
|
|
|
309
338
|
Agent(subagent_type="Git", run_in_background=false):
|
|
310
339
|
"OPERATION: post-review-summary
|
|
311
340
|
PR_NUMBER: {pr_number}
|
|
312
|
-
REVIEW_SUMMARY_PATH:
|
|
341
|
+
REVIEW_SUMMARY_PATH: .devflow/docs/reviews/{branch-slug}/{timestamp}/review-summary.md
|
|
313
342
|
CYCLE_NUMBER: {cycle_number}
|
|
314
343
|
REVIEW_TIMESTAMP: {timestamp}
|
|
315
344
|
REVIEW_PUBLICATION: {REVIEW_PUBLICATION}
|
|
@@ -327,7 +356,7 @@ Per worktree, after successful completion:
|
|
|
327
356
|
- Merge recommendation (from Synthesize agent)
|
|
328
357
|
- Issue counts by category (🔴 blocking / ⚠️ should-fix / ℹ️ pre-existing)
|
|
329
358
|
- Review comment status: POSTED / POSTED+TRUNCATED (body exceeded 60k after redaction) / SKIPPED (D7 dedup) / DEGRADED (from Git)
|
|
330
|
-
- Publication status: FULL (private repo) | FULL (config override) | STUB (public repository) | OFF (publication disabled by config) (from Git agent)
|
|
359
|
+
- Publication status: FULL (private repo) | FULL (config override) | STUB (public repository) | OFF (publication disabled by config) | STUB (visibility undeterminable) | STUB (evidence policy) (from Git agent)
|
|
331
360
|
- Artifact paths
|
|
332
361
|
|
|
333
362
|
In multi-worktree mode, report results per worktree.
|
|
@@ -378,14 +407,14 @@ In multi-worktree mode, report results per worktree.
|
|
|
378
407
|
| Worktree pre-flight fails | Report failure, continue with other worktrees |
|
|
379
408
|
| `--full` in multi-worktree mode | Applies to all worktrees (global modifier) |
|
|
380
409
|
| Many worktrees (5+) | Report count and proceed — user manages their worktree count |
|
|
381
|
-
| Review comment already posted | Git agent matches
|
|
410
|
+
| Review comment already posted | Git agent matches its own marker on the `{cycle, timestamp}` pair → skip silently (D7); a re-review in the same cycle (different timestamp) posts its own comment. The marker's format belongs to the operation; this caller passes the pair and never restates the literal |
|
|
382
411
|
| First review (no prior resolution) | PRIOR_RESOLUTIONS=(none), no convergence check |
|
|
383
412
|
| fp_ratio denominator = 0 | fp_ratio = 0, no warning |
|
|
384
413
|
| `--full` flag | Bypass incremental detection (Step 0d), still load PRIOR_RESOLUTIONS for cross-cycle awareness |
|
|
385
414
|
| Parsing failure on resolution-summary.md | fp_ratio = 0, convergence tracking degraded (see Step 0e-ii) |
|
|
386
415
|
| Concurrent sessions | Advisory only, each session computes independently |
|
|
387
416
|
| Public repo (`auto` mode) | Git agent probes visibility, posts counts-only STUB comment; full report stays local |
|
|
388
|
-
| `reviewPublication: off` | Git agent skips posting the review comment (OFF) |
|
|
417
|
+
| `reviewPublication: off` | Git agent skips posting the review comment (OFF) — except that, only when `EVIDENCE_POLICY` is `required`, Step 0f passes `stub` and a counts-only STUB comment posts |
|
|
389
418
|
|
|
390
419
|
## Backwards Compatibility
|
|
391
420
|
|