devflow-kit 3.6.0 → 3.8.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 +438 -1
- package/README.md +15 -11
- package/dist/agents/code.md +9 -15
- package/dist/agents/design.md +1 -1
- package/dist/agents/fix.md +11 -13
- package/dist/agents/git.md +1 -1
- package/dist/agents/harden.md +1 -1
- package/dist/agents/refactor.md +7 -10
- package/dist/agents/review.md +6 -4
- package/dist/agents/skim.md +2 -2
- package/dist/agents/triage.md +2 -2
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/curation-move-io.js +102 -0
- package/dist/cli/commands/curation-move-step.js +374 -0
- package/dist/cli/commands/{learning.js → curation.js} +302 -99
- package/dist/cli/commands/gate-level-prompts.js +426 -0
- package/dist/cli/commands/gates.js +299 -0
- package/dist/cli/commands/init-seed.js +16 -5
- package/dist/cli/commands/init.js +263 -54
- package/dist/cli/commands/memory.js +20 -2
- package/dist/cli/commands/prompt-io.js +37 -0
- package/dist/cli/commands/uninstall.js +81 -9
- package/dist/cli.js +4 -2
- package/dist/commands/bug-analysis.md +25 -36
- package/dist/commands/code-review.md +77 -73
- package/dist/commands/debug.md +5 -5
- package/dist/commands/dynamic-build.md +76 -83
- package/dist/commands/dynamic-plan.md +5 -5
- package/dist/commands/dynamic-profile.md +5 -5
- package/dist/commands/dynamic-tickets.md +5 -5
- package/dist/commands/explore.md +6 -6
- package/dist/commands/implement.md +68 -102
- package/dist/commands/plan.md +19 -29
- package/dist/commands/release.md +5 -5
- package/dist/commands/research.md +6 -6
- package/dist/commands/resolve.md +29 -31
- package/dist/commands/self-review.md +5 -13
- package/dist/core/assets.js +20 -20
- package/dist/core/curate-tuning-config.js +5 -6
- package/dist/core/curation-move-notice.js +84 -0
- package/dist/core/curation-move-skip.js +36 -0
- package/dist/core/curation-move.js +35 -0
- package/dist/core/curation-queue-cleanup.js +134 -0
- package/dist/core/curation-store.js +98 -0
- package/dist/core/{learning-variants.js → curation-variants.js} +65 -41
- package/dist/core/echo-value.js +48 -0
- package/dist/core/evidence-policy.js +18 -6
- package/dist/core/feature-switch.js +95 -19
- package/dist/core/fs-atomic.js +1 -1
- package/dist/core/gate-level.js +221 -0
- package/dist/core/git.js +16 -14
- package/dist/core/ledger-root.js +82 -22
- package/dist/core/linked-path.js +4 -4
- package/dist/core/manifest-lock.js +238 -0
- package/dist/core/manifest.js +84 -24
- package/dist/core/migrations.js +1 -13
- package/dist/core/model-discovery.js +7 -1
- package/dist/core/observations.js +2 -2
- package/dist/core/plugins.js +15 -15
- package/dist/core/pool.js +26 -0
- package/dist/core/project-paths.js +103 -34
- package/dist/core/queue-drain.js +2 -2
- package/dist/core/tracked-areas.js +35 -0
- package/dist/core/tracker.js +6 -34
- package/dist/{learning-off → curation-off}/agents/code.md +9 -15
- package/dist/{learning-off → curation-off}/agents/fix.md +11 -13
- package/dist/{learning-off → curation-off}/agents/refactor.md +7 -10
- package/dist/{learning-off → curation-off}/agents/review.md +5 -3
- package/dist/{learning-off → curation-off}/agents/triage.md +1 -1
- package/dist/{learning-off → curation-off}/commands/bug-analysis.md +20 -31
- package/dist/{learning-off → curation-off}/commands/code-review.md +74 -70
- package/dist/{learning-off → curation-off}/commands/debug.md +2 -2
- package/dist/{learning-off → curation-off}/commands/dynamic-build.md +73 -80
- package/dist/{learning-off → curation-off}/commands/explore.md +2 -2
- package/dist/{learning-off → curation-off}/commands/implement.md +65 -99
- package/dist/{learning-off → curation-off}/commands/plan.md +15 -25
- package/dist/{learning-off → curation-off}/commands/resolve.md +26 -28
- package/dist/{learning-off → curation-off}/commands/self-review.md +2 -10
- package/dist/hud/components/{learning-counts.js → curation-counts.js} +36 -17
- package/dist/hud/config.js +1 -1
- package/dist/hud/index.js +6 -6
- package/dist/hud/render.js +3 -3
- package/dist/skills/git/references/pr/validate-branch.md +8 -5
- package/dist/skills/git/references/tracker/github/ensure-pr-ready.md +20 -14
- package/dist/skills/git/references/tracker/jira/ensure-pr-ready.md +20 -20
- package/dist/skills/git/references/tracker/linear/ensure-pr-ready.md +20 -20
- package/dist/targets/claude-code/{learning-install.js → curation-install.js} +99 -67
- package/dist/targets/claude-code/installer.js +15 -15
- package/dist/targets/claude-code/language-stamp.js +5 -5
- package/dist/targets/claude-code/post-install.js +2 -2
- package/dist/targets/claude-code/tracker-servers.js +1 -1
- package/package.json +3 -2
- package/src/assets/agents/_partials/_code.mds +16 -17
- package/src/assets/agents/code.mds +4 -7
- package/src/assets/agents/curate.md +12 -14
- package/src/assets/agents/debug.mds +6 -6
- package/src/assets/agents/design.mds +18 -18
- package/src/assets/agents/fix.mds +6 -5
- package/src/assets/agents/git.mds +1 -1
- package/src/assets/agents/harden.mds +7 -7
- package/src/assets/agents/knowledge.mds +23 -23
- package/src/assets/agents/refactor.mds +2 -2
- package/src/assets/agents/research.mds +15 -15
- package/src/assets/agents/review.mds +45 -43
- package/src/assets/agents/skim.mds +11 -11
- package/src/assets/agents/synthesize.md +2 -2
- package/src/assets/agents/triage.mds +22 -22
- package/src/assets/agents/validate.md +13 -26
- package/src/assets/commands/_partials/_decisions.mds +13 -13
- package/src/assets/commands/_partials/_engine.mds +8 -8
- package/src/assets/commands/_partials/_knowledge.mds +29 -15
- package/src/assets/commands/_partials/_preamble.mds +4 -4
- package/src/assets/commands/_partials/_settings.mds +3 -3
- package/src/assets/commands/_partials/_wave.mds +17 -24
- package/src/assets/commands/bug-analysis.mds +37 -48
- package/src/assets/commands/code-review.mds +90 -86
- package/src/assets/commands/debug.mds +14 -14
- package/src/assets/commands/dynamic-build.mds +78 -78
- package/src/assets/commands/dynamic-plan.mds +22 -22
- package/src/assets/commands/dynamic-profile.mds +2 -2
- package/src/assets/commands/dynamic-tickets.mds +8 -8
- package/src/assets/commands/explore.mds +20 -20
- package/src/assets/commands/implement.mds +92 -118
- package/src/assets/commands/plan.mds +41 -51
- package/src/assets/commands/release.mds +12 -12
- package/src/assets/commands/research.mds +19 -19
- package/src/assets/commands/resolve.mds +56 -50
- package/src/assets/commands/self-review.mds +20 -20
- package/src/assets/mds/git/_ops.mds +3 -1
- package/src/assets/mds/git/_ops_pr.mds +1 -1
- package/src/assets/mds/git/_ops_steps.mds +13 -7
- package/src/assets/mds/git/_pr.mds +7 -4
- package/src/assets/mds/tracker/_common.mds +23 -12
- package/src/assets/mds/tracker/_github.mds +2 -9
- package/src/assets/mds/tracker/_jira.mds +2 -15
- package/src/assets/mds/tracker/_linear.mds +2 -15
- package/src/assets/rules/quality.md +1 -0
- package/src/assets/scripts/ci-wait.cjs +22 -4
- package/src/assets/scripts/claude-md-audit.cjs +66 -51
- package/src/assets/scripts/hooks/background-memory-update +406 -97
- package/src/assets/scripts/hooks/capture-prompt +12 -12
- package/src/assets/scripts/hooks/capture-question +11 -11
- package/src/assets/scripts/hooks/capture-turn +12 -12
- package/src/assets/scripts/hooks/{learning-lock → curation-lock} +2 -2
- package/src/assets/scripts/hooks/curation-move +293 -0
- package/src/assets/scripts/hooks/ensure-devflow-init +45 -13
- package/src/assets/scripts/hooks/ensure-proxy +4 -4
- package/src/assets/scripts/hooks/ensure-root-gitignore +4 -4
- package/src/assets/scripts/hooks/git-marker +81 -0
- package/src/assets/scripts/hooks/json-helper.cjs +145 -51
- package/src/assets/scripts/hooks/json-parse +19 -4
- package/src/assets/scripts/hooks/lib/curation-move.cjs +1582 -0
- package/src/assets/scripts/hooks/lib/{learning-store.cjs → curation-store.cjs} +242 -118
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +3 -3
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +24 -7
- package/src/assets/scripts/hooks/lib/project-paths.cjs +132 -39
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +34 -22
- package/src/assets/scripts/hooks/lib/tracked-areas.cjs +259 -0
- package/src/assets/scripts/hooks/memory-permission +358 -0
- package/src/assets/scripts/hooks/memory-worker +1 -1
- package/src/assets/scripts/hooks/pre-compact-memory +6 -16
- package/src/assets/scripts/hooks/queue-append +228 -103
- package/src/assets/scripts/hooks/resolve-project-root +2 -2
- package/src/assets/scripts/hooks/session-start-context +145 -71
- package/src/assets/scripts/hooks/session-start-memory +27 -46
- package/src/assets/scripts/lib/project-config.cjs +31 -9
- package/src/assets/scripts/resolve-base.cjs +1043 -0
- package/src/assets/scripts/resolve-settings.cjs +238 -46
- package/src/assets/skills/agent-references/SKILL.md +9 -2
- package/src/assets/skills/agent-references/references/commands/code-review-levels.md +19 -0
- package/src/assets/skills/agent-references/references/commands/implement-levels.md +27 -0
- package/src/assets/skills/agent-references/references/commands/implement-revalidate.md +6 -4
- package/src/assets/skills/agent-references/references/commands/plan-levels.md +40 -0
- package/src/assets/skills/agent-references/references/commands/resolve-levels.md +24 -0
- package/src/assets/skills/agent-references/references/synthesize/planning.md +9 -22
- package/src/assets/skills/agent-references/references/synthesize/review.md +11 -11
- package/src/assets/skills/apply-decisions/SKILL.md +1 -1
- package/src/assets/skills/docs-framework/SKILL.md +7 -7
- package/src/assets/skills/docs-framework/references/patterns.md +3 -4
- package/src/assets/skills/quality-gates/SKILL.md +1 -1
- package/src/assets/skills/worktree-support/references/roots.md +2 -2
- package/templates/lint-pack/.lint-pack.conf +86 -0
- package/templates/lint-pack/README.md +190 -0
- package/templates/lint-pack/lint-pack.sh +905 -0
- package/dist/core/learning-queue-cleanup.js +0 -27
- package/dist/core/learning-store.js +0 -61
- /package/dist/{learning-off → curation-off}/agents/debug.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/design.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/harden.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/knowledge.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/research.md +0 -0
- /package/dist/{learning-off → curation-off}/agents/skim.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-plan.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-profile.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/dynamic-tickets.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/release.md +0 -0
- /package/dist/{learning-off → curation-off}/commands/research.md +0 -0
|
@@ -40,7 +40,7 @@ const MEMORY_HOOK_CONFIG = {
|
|
|
40
40
|
*
|
|
41
41
|
* D-EXACT-HOOK-OWNER (hooks.ts): matched as command ENDINGS under any directory —
|
|
42
42
|
* the v1 (≤ v1.2) direct `.sh` scripts, then the retired `run-hook` markers of the
|
|
43
|
-
* prompt-capture,
|
|
43
|
+
* prompt-capture, curation, decisions, knowledge-refresh, sidecar and dream
|
|
44
44
|
* pipelines — never as substrings, so a user's hook that mentions one is theirs.
|
|
45
45
|
*/
|
|
46
46
|
const LEGACY_HOOK_SUFFIXES = {
|
|
@@ -56,6 +56,24 @@ const LEGACY_HOOK_SUFFIXES = {
|
|
|
56
56
|
'sidecar-evaluate', 'dream-evaluate',
|
|
57
57
|
].map(runHookSuffix),
|
|
58
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* The lowest Claude Code the memory worker runs on: the lowest version on which the
|
|
61
|
+
* worker's write was measured to be limited to its staged file (2026-10-15,
|
|
62
|
+
* docs/reference/platform-assumptions.md). Below it the worker does not run and logs one
|
|
63
|
+
* line. `background-memory-update` holds the same value as MEMORY_MIN_PERMISSION_VERSION;
|
|
64
|
+
* a test holds the two equal.
|
|
65
|
+
*/
|
|
66
|
+
export const MEMORY_MIN_PERMISSION_VERSION = '2.1.269';
|
|
67
|
+
/** The note `devflow init` shows at its working-memory step. */
|
|
68
|
+
export function workingMemoryNote() {
|
|
69
|
+
return ('Preserves session context across /clear, restarts, and context\n' +
|
|
70
|
+
'compaction. Clear your session at any point and resume right\n' +
|
|
71
|
+
'where you left off.\n\n' +
|
|
72
|
+
'Runs a background agent on session stop that consumes additional\n' +
|
|
73
|
+
'tokens. Consider skipping if token usage is a concern.\n' +
|
|
74
|
+
`Needs Claude Code ${MEMORY_MIN_PERMISSION_VERSION} or newer.\n` +
|
|
75
|
+
'Applies to every project.');
|
|
76
|
+
}
|
|
59
77
|
/** D-EXACT-HOOK-OWNER: the current memory hook for `marker` (hooks.ts). */
|
|
60
78
|
function isMemoryHook(marker) {
|
|
61
79
|
return devflowHookOwner([marker]);
|
|
@@ -147,7 +165,7 @@ export function convergeMemoryHooks(settingsJson, enabled, devflowDir) {
|
|
|
147
165
|
* --no-memory` and `devflow memory --disable`. Refused, deleting nothing, when
|
|
148
166
|
* `.devflow` or `.devflow/memory` under `projectRoot` is a symbolic link
|
|
149
167
|
* (D-CLI-NO-SYMLINK). ENOENT-tolerant; any other error propagates to the command
|
|
150
|
-
* boundary, like
|
|
168
|
+
* boundary, like drainCurationQueue.
|
|
151
169
|
*/
|
|
152
170
|
export async function drainMemoryQueue(projectRoot) {
|
|
153
171
|
const memoryDir = getMemoryDir(projectRoot);
|
|
@@ -9,8 +9,45 @@
|
|
|
9
9
|
* D-PROMPT-IO: WizardPromptIO is the base DI seam for all two-action wizard
|
|
10
10
|
* steps (note + boolean select). Modules that add a third prompt extend this
|
|
11
11
|
* interface with an intersection type (e.g. CompliancePromptIO).
|
|
12
|
+
*
|
|
13
|
+
* The shared wizard gate predicate (shouldRunPromptedWizardStep) lives here too,
|
|
14
|
+
* beside the seam every prompted step already imports.
|
|
12
15
|
*/
|
|
13
16
|
import * as p from '@clack/prompts';
|
|
17
|
+
// ── Shared gate predicate ─────────────────────────────────────────────────────
|
|
18
|
+
/**
|
|
19
|
+
* Determines whether a wizard step that runs on both wizard paths should run for a
|
|
20
|
+
* given init invocation.
|
|
21
|
+
*
|
|
22
|
+
* Gate table (key on modePromptShown, never on the mode name):
|
|
23
|
+
*
|
|
24
|
+
* --recommended flag / !isTTY fallback → no (promptless contract preserved)
|
|
25
|
+
* Interactive mode-prompt → Recommended → yes (modePromptShown=true)
|
|
26
|
+
* --advanced flag / re-init (banner path) → yes (mode='advanced', isTTY=true)
|
|
27
|
+
* Interactive mode-prompt → Advanced → yes (modePromptShown=true)
|
|
28
|
+
* Any path with the step's CLI flag → no (hasCliOverride wins)
|
|
29
|
+
*
|
|
30
|
+
* A step that wires only the Recommended path is dead on every re-init, because re-init
|
|
31
|
+
* is Advanced-only by construction, so each step calls this predicate from both paths.
|
|
32
|
+
* The gate-level step delegates here. The attribution step is Advanced-only by design
|
|
33
|
+
* and does not use this table.
|
|
34
|
+
*
|
|
35
|
+
* Pure predicate — no side effects, fully testable without a TTY.
|
|
36
|
+
*/
|
|
37
|
+
export function shouldRunPromptedWizardStep(input) {
|
|
38
|
+
if (input.hasCliOverride)
|
|
39
|
+
return false;
|
|
40
|
+
if (!input.isTTY)
|
|
41
|
+
return false;
|
|
42
|
+
// Advanced path: non-TTY has already exit-1'd, so isTTY=true here → always run.
|
|
43
|
+
// Covers: --advanced flag, re-init banner path, interactive-prompt → advanced.
|
|
44
|
+
if (input.mode === 'advanced')
|
|
45
|
+
return true;
|
|
46
|
+
// Recommended path: only run when the Setup-mode p.select actually ran
|
|
47
|
+
// (user made an active choice). --recommended flag and !isTTY fallback never set
|
|
48
|
+
// modePromptShown=true, preserving their promptless contracts.
|
|
49
|
+
return input.modePromptShown;
|
|
50
|
+
}
|
|
14
51
|
// ── Shared clack adapters ─────────────────────────────────────────────────────
|
|
15
52
|
/** Real clack adapter for the note prompt. */
|
|
16
53
|
export function clackNote(message, title) {
|
|
@@ -31,6 +31,8 @@ import { stripFlags } from '../../core/flags.js';
|
|
|
31
31
|
import { stripDevflowTeammateModeFromJson } from '../../core/teammate-mode-cleanup.js';
|
|
32
32
|
import { getPackageRoot, isContainedIn } from '../../core/paths.js';
|
|
33
33
|
import { firstSymbolicLink } from '../../core/linked-path.js';
|
|
34
|
+
import { MANIFEST_LOCK_NAME, MANIFEST_LOCK_STALE_MARK } from '../../core/manifest-lock.js';
|
|
35
|
+
import { CURATION_TUNING_FILE_NAME, LEGACY_NAMES, MOVE_JOURNAL_SUFFIX, MOVE_LOCK_PREFIX, MOVE_LOCK_SUFFIX } from '../../core/project-paths.js';
|
|
34
36
|
import { restampInstalledCommands } from '../../targets/claude-code/language-stamp.js';
|
|
35
37
|
import { installCommandsAndAgents } from '../../targets/claude-code/installer.js';
|
|
36
38
|
/**
|
|
@@ -489,8 +491,10 @@ export function userContentPaths(devflowDir) {
|
|
|
489
491
|
const name = `${TRACKER_LEGACY_CONVENTIONS_FILE}.${id}.bak`;
|
|
490
492
|
return { relPath: name, label: `${name} (previous issue tracker conventions)` };
|
|
491
493
|
}),
|
|
492
|
-
//
|
|
493
|
-
|
|
494
|
+
// The global curate agent tuning config, under both its names: the new name on an
|
|
495
|
+
// upgraded machine, and the old name, which a downgraded machine can still hold.
|
|
496
|
+
{ relPath: CURATION_TUNING_FILE_NAME, label: CURATION_TUNING_FILE_NAME },
|
|
497
|
+
{ relPath: LEGACY_NAMES.tuningFile, label: LEGACY_NAMES.tuningFile },
|
|
494
498
|
// hud.json — user HUD enable/disable preference and display config
|
|
495
499
|
{ relPath: 'hud.json', label: 'hud.json (HUD configuration)' },
|
|
496
500
|
];
|
|
@@ -527,6 +531,54 @@ export async function enumerateUserDevFlowContent(devflowDir) {
|
|
|
527
531
|
}
|
|
528
532
|
return items;
|
|
529
533
|
}
|
|
534
|
+
/** `text` as a regular-expression source, every character literal. */
|
|
535
|
+
function escapeRegExp(text) {
|
|
536
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
537
|
+
}
|
|
538
|
+
/** The name of a move lock or journal: the shared prefix, a 16-digit lowercase hex key, then `suffix`. */
|
|
539
|
+
function moveFileShape(suffix) {
|
|
540
|
+
return new RegExp(`^${escapeRegExp(MOVE_LOCK_PREFIX)}[0-9a-f]{16}${escapeRegExp(suffix)}$`);
|
|
541
|
+
}
|
|
542
|
+
/**
|
|
543
|
+
* The curation move's lock, one directory per ledger root, taken directly in the machine
|
|
544
|
+
* directory: `.curation-move-<16 lowercase hex>.lock`. The store builds the name from a
|
|
545
|
+
* hash of the real ledger root, so the names exist only on disk. The prefix and the
|
|
546
|
+
* suffix are the ones the path helpers hold (project-paths.ts).
|
|
547
|
+
*/
|
|
548
|
+
const CURATION_MOVE_LOCK_SHAPE = moveFileShape(MOVE_LOCK_SUFFIX);
|
|
549
|
+
/**
|
|
550
|
+
* The curation move's journal, one regular file per ledger root, beside the lock:
|
|
551
|
+
* `.curation-move-<16 lowercase hex>.journal`, with the same key as the lock. It exists
|
|
552
|
+
* only while a set move is unfinished, and a crash leaves it behind. A file named
|
|
553
|
+
* `.curation-move.journal` inside a project's old ledger folder is no journal of the
|
|
554
|
+
* move and is not matched here.
|
|
555
|
+
*/
|
|
556
|
+
const CURATION_MOVE_JOURNAL_SHAPE = moveFileShape(MOVE_JOURNAL_SUFFIX);
|
|
557
|
+
/**
|
|
558
|
+
* The manifest lock, a directory in the machine directory (manifest-lock.ts), and the
|
|
559
|
+
* directory a stale break moves aside: `manifest.json.lock` and
|
|
560
|
+
* `manifest.json.lock.stale-<digits>-<12 lowercase hex>`. A holder that dies in its
|
|
561
|
+
* critical section leaves the first, a break whose removal failed leaves the second.
|
|
562
|
+
*/
|
|
563
|
+
const MANIFEST_LOCK_SHAPE = new RegExp(`^${escapeRegExp(MANIFEST_LOCK_NAME)}(?:${escapeRegExp(MANIFEST_LOCK_STALE_MARK)}[0-9]+-[0-9a-f]{12})?$`);
|
|
564
|
+
/** Whether `child` of `devflowDir` is a real directory (a link is not followed). */
|
|
565
|
+
async function isRealDirectory(devflowDir, child) {
|
|
566
|
+
try {
|
|
567
|
+
return (await fs.lstat(path.join(devflowDir, child))).isDirectory();
|
|
568
|
+
}
|
|
569
|
+
catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/** Whether `child` of `devflowDir` is a regular file (a link is not followed). */
|
|
574
|
+
async function isRegularFile(devflowDir, child) {
|
|
575
|
+
try {
|
|
576
|
+
return (await fs.lstat(path.join(devflowDir, child))).isFile();
|
|
577
|
+
}
|
|
578
|
+
catch {
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
530
582
|
/**
|
|
531
583
|
* Expand `installArtifactPaths` against real disk.
|
|
532
584
|
*
|
|
@@ -553,8 +605,15 @@ export async function resolveInstallArtifactPaths(devflowDir) {
|
|
|
553
605
|
}
|
|
554
606
|
}
|
|
555
607
|
for (const child of children) {
|
|
556
|
-
if (child.startsWith(entry.relPath))
|
|
557
|
-
|
|
608
|
+
if (!child.startsWith(entry.relPath))
|
|
609
|
+
continue;
|
|
610
|
+
if (entry.shape !== undefined && !entry.shape.test(child))
|
|
611
|
+
continue;
|
|
612
|
+
if (entry.realDirOnly === true && !(await isRealDirectory(devflowDir, child)))
|
|
613
|
+
continue;
|
|
614
|
+
if (entry.realFileOnly === true && !(await isRegularFile(devflowDir, child)))
|
|
615
|
+
continue;
|
|
616
|
+
resolved.push({ relPath: child, isDir: entry.isDir });
|
|
558
617
|
}
|
|
559
618
|
}
|
|
560
619
|
return resolved;
|
|
@@ -621,6 +680,18 @@ export function installArtifactPaths(devflowDir) {
|
|
|
621
680
|
// A prefix, because the names exist only on disk. Content is a scrubbed copy
|
|
622
681
|
// that was never placed, so it is machine state like the three above.
|
|
623
682
|
{ relPath: TRACKER_STAGED_PREFIX, isPrefix: true },
|
|
683
|
+
// The curation move's per-root lock directory. A crash between taking the lock and
|
|
684
|
+
// releasing it leaves one behind, and its name carries a hash that exists only on disk.
|
|
685
|
+
// Only a real directory of exactly that shape goes; the move's skip markers sit under
|
|
686
|
+
// logs/ and go with it.
|
|
687
|
+
{ relPath: MOVE_LOCK_PREFIX, isPrefix: true, isDir: true, shape: CURATION_MOVE_LOCK_SHAPE, realDirOnly: true },
|
|
688
|
+
// The move's journal beside it. A crash between two renames of the ledger set leaves
|
|
689
|
+
// one behind. Only a regular file of exactly that shape goes.
|
|
690
|
+
{ relPath: MOVE_LOCK_PREFIX, isPrefix: true, shape: CURATION_MOVE_JOURNAL_SHAPE, realFileOnly: true },
|
|
691
|
+
// The manifest lock, and the directory a stale break moved aside. A crash inside the
|
|
692
|
+
// critical section leaves the first; a failed cleanup leaves the second, whose name
|
|
693
|
+
// carries a pid and a random suffix. Only a real directory of exactly that shape goes.
|
|
694
|
+
{ relPath: MANIFEST_LOCK_NAME, isPrefix: true, isDir: true, shape: MANIFEST_LOCK_SHAPE, realDirOnly: true },
|
|
624
695
|
// per-project hook logs (logs/{project-slug}/) AND global logs — remove the
|
|
625
696
|
// whole logs/ tree; covers proxy.log, debug logs, and any project-slug dirs.
|
|
626
697
|
{ relPath: 'logs', isDir: true },
|
|
@@ -644,7 +715,8 @@ export function installArtifactPaths(devflowDir) {
|
|
|
644
715
|
* so an entry here is deleted even when the user answers "no" to the full wipe.
|
|
645
716
|
* User-authored state (everything in `userContentPaths`: skill/rule shadows,
|
|
646
717
|
* preference-profile.md, the tracker conventions with the legacy tracker.md and
|
|
647
|
-
* its backups,
|
|
718
|
+
* its backups, the global tuning file under both names, hud.json) is removed only by
|
|
719
|
+
* the confirmed full-dir rm.
|
|
648
720
|
* agent-models.json is an INSTALL ARTIFACT (stale per-agent overrides silently
|
|
649
721
|
* re-apply to renamed/deleted agents on reinstall — AC-P1-F4) and therefore
|
|
650
722
|
* belongs in this list, not in enumerateUserDevFlowContent.
|
|
@@ -953,7 +1025,7 @@ export async function refreshRetainedCommandsAndAgents(opts) {
|
|
|
953
1025
|
plugins: retained,
|
|
954
1026
|
agentsMap: buildAssetMaps(retained).agentsMap,
|
|
955
1027
|
claudeDir,
|
|
956
|
-
|
|
1028
|
+
curation: manifest.features.curation,
|
|
957
1029
|
trackerServers: effectiveTrackerServers(manifest.features.tracker),
|
|
958
1030
|
warn: (msg) => p.log.warn(msg),
|
|
959
1031
|
});
|
|
@@ -1073,7 +1145,7 @@ export async function runSelectivePhaseForScope(opts) {
|
|
|
1073
1145
|
* FULL PHASE (per scope): revert agent model overrides, remove all Devflow
|
|
1074
1146
|
* assets from the scope, then perform scope-appropriate devflowDir cleanup.
|
|
1075
1147
|
*
|
|
1076
|
-
* Local scope: remove only install artifacts — project data (memory,
|
|
1148
|
+
* Local scope: remove only install artifacts — project data (memory, curation,
|
|
1077
1149
|
* docs, features) is always preserved under the git root.
|
|
1078
1150
|
* User scope: interactive TTY with user-authored content → confirm before wiping
|
|
1079
1151
|
* ~/.devflow/; non-interactive or no user content → artifacts-only.
|
|
@@ -1115,11 +1187,11 @@ export async function runFullPhaseForScope(opts) {
|
|
|
1115
1187
|
await removeAllDevFlow(claudeDir, devflowScriptsDir, verbose, mayChange);
|
|
1116
1188
|
if (scope === 'local') {
|
|
1117
1189
|
// Local scope: devflowDir is gitRoot/.devflow/ which holds project data
|
|
1118
|
-
// (memory,
|
|
1190
|
+
// (memory, curation, features, docs, config.json). Never remove those —
|
|
1119
1191
|
// only remove install artifacts: scripts/ (done above) + manifest.json and
|
|
1120
1192
|
// the other Devflow-generated artifacts (see removeDevFlowInstallArtifacts).
|
|
1121
1193
|
await removeDevFlowInstallArtifacts(devflowDir, verbose, mayChange);
|
|
1122
|
-
p.log.info('Local project data (memory,
|
|
1194
|
+
p.log.info('Local project data (memory, curation, features, docs) preserved');
|
|
1123
1195
|
}
|
|
1124
1196
|
else {
|
|
1125
1197
|
// User scope (devflowDir = ~/.devflow/): offer full cleanup behind a confirm gate
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { skillsCommand } from './cli/commands/skills.js';
|
|
|
11
11
|
import { hudCommand } from './cli/commands/hud.js';
|
|
12
12
|
import { flagsCommand } from './cli/commands/flags.js';
|
|
13
13
|
import { knowledgeCommand } from './cli/commands/knowledge/index.js';
|
|
14
|
-
import {
|
|
14
|
+
import { curationCommand } from './cli/commands/curation.js';
|
|
15
15
|
import { rulesCommand } from './cli/commands/rules.js';
|
|
16
16
|
import { debugCommand } from './cli/commands/debug.js';
|
|
17
17
|
import { securityCommand } from './cli/commands/security.js';
|
|
@@ -21,6 +21,7 @@ import { agentsCommand } from './cli/commands/agents.js';
|
|
|
21
21
|
import { complianceCommand } from './cli/commands/compliance.js';
|
|
22
22
|
import { behaviorCommand } from './cli/commands/behavior.js';
|
|
23
23
|
import { trackerCommand } from './cli/commands/tracker.js';
|
|
24
|
+
import { gatesCommand } from './cli/commands/gates.js';
|
|
24
25
|
const __filename = fileURLToPath(import.meta.url);
|
|
25
26
|
const __dirname = dirname(__filename);
|
|
26
27
|
// Read version from package.json
|
|
@@ -41,7 +42,7 @@ program.addCommand(skillsCommand);
|
|
|
41
42
|
program.addCommand(hudCommand);
|
|
42
43
|
program.addCommand(flagsCommand);
|
|
43
44
|
program.addCommand(knowledgeCommand);
|
|
44
|
-
program.addCommand(
|
|
45
|
+
program.addCommand(curationCommand);
|
|
45
46
|
program.addCommand(rulesCommand);
|
|
46
47
|
program.addCommand(debugCommand);
|
|
47
48
|
program.addCommand(securityCommand);
|
|
@@ -51,6 +52,7 @@ program.addCommand(agentsCommand);
|
|
|
51
52
|
program.addCommand(complianceCommand);
|
|
52
53
|
program.addCommand(behaviorCommand);
|
|
53
54
|
program.addCommand(trackerCommand);
|
|
55
|
+
program.addCommand(gatesCommand);
|
|
54
56
|
// Handle no command (bare `devflow`) or unknown subcommand.
|
|
55
57
|
// When Commander sees an unrecognised first argument it does not route to any
|
|
56
58
|
// registered subcommand; instead the root action fires with that argument
|
|
@@ -3,7 +3,7 @@ description: Proactive bug finding with static and semantic analysis — hunts r
|
|
|
3
3
|
---
|
|
4
4
|
# Bug Analysis Command
|
|
5
5
|
|
|
6
|
-
Run a proactive bug analysis on the current branch by combining static analysis tools with parallel semantic analyzers, then synthesizing results into an actionable bug report.
|
|
6
|
+
Run a proactive bug analysis on the current branch by combining static analysis tools with parallel semantic analyzers, then synthesizing results into an actionable bug report.
|
|
7
7
|
|
|
8
8
|
## Usage
|
|
9
9
|
|
|
@@ -55,16 +55,16 @@ Agent(subagent_type="Git", run_in_background=false):
|
|
|
55
55
|
PR_TEST_PLAN_BLOCK: {PR_TEST_PLAN_BLOCK verbatim, or (none)}
|
|
56
56
|
APPLY_CONVENTIONS: {APPLY_CONVENTIONS}
|
|
57
57
|
Validate branch, commit if needed, push, create PR if needed.
|
|
58
|
-
Return: branch, base_branch, branch-slug, PR#"
|
|
58
|
+
Return: branch, base_branch, base_ref, branch-slug, PR#"
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
**Extract from response:** `branch`, `base_branch`, `branch_slug`, `pr_number`.
|
|
61
|
+
**Extract from response:** `branch`, `base_branch`, `base_ref`, `branch_slug`, `pr_number`. Stop with cause and fix when the Base line reads (rejected) or `branch_slug` does not match the slug pattern above; name the Git agent's fix, never your own, and for the slug the allowed characters, never its value.
|
|
62
62
|
|
|
63
63
|
**Fetch PR body** (after extracting `pr_number`):
|
|
64
64
|
```bash
|
|
65
65
|
PR_DESCRIPTION=$(gh pr view {pr_number} --json body --jq '.body' 2>/dev/null || echo "(none)")
|
|
66
66
|
```
|
|
67
|
-
If `pr_number` is absent
|
|
67
|
+
If `pr_number` is absent, set `PR_DESCRIPTION` to `(none)`.
|
|
68
68
|
|
|
69
69
|
### Phase 2: Static Analysis
|
|
70
70
|
|
|
@@ -75,14 +75,15 @@ If `pr_number` is absent or the command fails, set `PR_DESCRIPTION` to `(none)`.
|
|
|
75
75
|
**Produces:** DIFF_RANGE, ANALYSIS_DIR
|
|
76
76
|
**Requires:** BRANCH_INFO
|
|
77
77
|
|
|
78
|
-
1. Check `{
|
|
78
|
+
1. Check `{gd}/devflow/bug-analysis/{branch-slug}/.last-analysis-head`, `{gd}` being what `git -C "{worktree}" rev-parse --absolute-git-dir` prints:
|
|
79
79
|
- **If exists AND `--full` NOT set:**
|
|
80
|
-
-
|
|
81
|
-
- Verify reachable: `git cat-file -t {sha}`
|
|
80
|
+
- The stored analysis head is checked as a commit ID: use it only when it is exactly 40 or 64 lowercase hex, else take the full-range path
|
|
81
|
+
- Verify reachable: `git -C "{worktree}" cat-file -t {sha}` must print `commit`; if not (rebase invalidated SHA), take the full-range path
|
|
82
82
|
- If SHA == current HEAD → "No new commits since last analysis. Use --full for a full re-analysis." Stop.
|
|
83
83
|
- Set `DIFF_RANGE` to `{sha}...HEAD`
|
|
84
|
-
- **
|
|
85
|
-
-
|
|
84
|
+
- **Full-range path (no marker, or `--full`):**
|
|
85
|
+
- Stop as for a (rejected) Base line unless `base_ref` is 40 or 64 hex.
|
|
86
|
+
- Set `DIFF_RANGE` to `{base_ref}...HEAD`
|
|
86
87
|
2. Generate timestamp: `YYYY-MM-DD_HHMM`. If directory already exists (same-minute collision), append seconds (`YYYY-MM-DD_HHMMSS`).
|
|
87
88
|
3. Create timestamped analysis directory: `mkdir -p "{worktree}/.devflow/docs/bug-analysis/{branch-slug}/{timestamp}/"`
|
|
88
89
|
4. Set `ANALYSIS_DIR` to that path.
|
|
@@ -95,7 +96,7 @@ If `pr_number` is absent or the command fails, set `PR_DESCRIPTION` to `(none)`.
|
|
|
95
96
|
CHANGED_FILES=$(git diff --name-only {DIFF_RANGE})
|
|
96
97
|
```
|
|
97
98
|
|
|
98
|
-
Store
|
|
99
|
+
Store as `CHANGED_FILES`; Steps 2d and Phase 4 read it. A non-zero diff exit stops.
|
|
99
100
|
|
|
100
101
|
If output is empty → "No changes to analyze." Stop.
|
|
101
102
|
|
|
@@ -124,7 +125,7 @@ Skip if `--no-static` flag provided or all tools unavailable.
|
|
|
124
125
|
|
|
125
126
|
Run available tools on the changed files (from `CHANGED_FILES` computed in Step 2b).
|
|
126
127
|
|
|
127
|
-
**Semgrep and Snyk run in parallel** — launch both in the background, then wait for both before proceeding to CodeQL. CodeQL is conditional and sequential
|
|
128
|
+
**Semgrep and Snyk run in parallel** — launch both in the background, then wait for both before proceeding to CodeQL. CodeQL is conditional and sequential.
|
|
128
129
|
|
|
129
130
|
**Semgrep** (if available):
|
|
130
131
|
```bash
|
|
@@ -135,11 +136,9 @@ Parse SARIF output → extract findings.
|
|
|
135
136
|
|
|
136
137
|
**Snyk Code** (if available):
|
|
137
138
|
```bash
|
|
138
|
-
#
|
|
139
|
-
# Per-file invocation via xargs would invoke snyk O(n) times and --file is for dependency scanning, not source code.
|
|
139
|
+
# One project-level scan (a per-file xargs run would invoke snyk O(n) times; --file is for dependency scans).
|
|
140
140
|
SNYK_SARIF=$(timeout 300 snyk code test --sarif 2>/dev/null)
|
|
141
|
-
# Programmatic filter: keep only results whose file path
|
|
142
|
-
# This is defense-in-depth — filtering at the data layer before the LLM agent sees any output.
|
|
141
|
+
# Programmatic filter: keep only results whose file path is in CHANGED_FILES — defense-in-depth, before the LLM agent sees any output.
|
|
143
142
|
SNYK_FILTERED=$(echo "$SNYK_SARIF" | jq --argjson files "$(echo "$CHANGED_FILES" | jq -R . | jq -s .)" \
|
|
144
143
|
'.runs[].results |= map(select(.locations[].physicalLocation.artifactLocation.uri as $uri | $files | index($uri) != null))' \
|
|
145
144
|
2>/dev/null || echo "$SNYK_SARIF")
|
|
@@ -165,7 +164,7 @@ trap - EXIT INT TERM
|
|
|
165
164
|
```
|
|
166
165
|
Parse `CODEQL_SARIF` → extract findings. If database creation fails, `CODEQL_EXIT` is non-zero — skip CodeQL findings and note it. The `trap` guarantees cleanup on SIGTERM/SIGINT/EXIT so orphaned temp directories do not accumulate across interrupted sessions.
|
|
167
166
|
|
|
168
|
-
**Normalize** all findings to unified table, cap at top 50 by severity. Truncate each Description entry to 200 characters maximum
|
|
167
|
+
**Normalize** all findings to unified table, cap at top 50 by severity. Truncate each Description entry to 200 characters maximum:
|
|
169
168
|
|
|
170
169
|
| Tool | File:Line | CWE | Severity | Title | Description |
|
|
171
170
|
|------|-----------|-----|----------|-------|-------------|
|
|
@@ -187,13 +186,13 @@ If no tool produced findings: set `STATIC_FINDINGS` to `(none)`.
|
|
|
187
186
|
node "$HOME/.devflow/scripts/resolve-settings.cjs" "{root}" 2>/dev/null; echo "exit=$?"
|
|
188
187
|
```
|
|
189
188
|
|
|
190
|
-
Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off>
|
|
189
|
+
Accept the output only when it is exactly two lines: `exit=0` last and, before it, one line of the form `TRACKER=<github|jira|linear> TRACKER_SOURCE=<project|personal|machine|default> TRACKER_WARN=<none|mismatch|invalid> SITE=<none|https://<host>> KEY=<none|<key>> REVIEW_PUBLICATION=<off|auto|full> COMPLIANCE=<off|generic|<id>[,<id>…]> MEMORY=<on|off> CURATION=<on|off> KNOWLEDGE=<on|off> GATE_LEVEL=<full|standard|lean>` — these fields, in this order, nothing else, where `<host>` is a lowercase dotted host name alone, `<key>` is 2–10 of `A-Z`, `0-9` and `_` starting with a letter, and each `<id>` is one of `gdpr`, `hipaa`, `pci-dss`, `soc2`, `iso-27001`, `sox`. **Anything else** (a non-zero exit, no line, extra text, or a missing, reordered or unlisted field or value) ⇒ use `TRACKER=github TRACKER_SOURCE=default TRACKER_WARN=invalid SITE=none KEY=none REVIEW_PUBLICATION=off COMPLIANCE=generic MEMORY=on CURATION=on KNOWLEDGE=off GATE_LEVEL=full` instead.
|
|
191
190
|
|
|
192
|
-
The accepted line is the only source of these values
|
|
191
|
+
The accepted line is the only source of these values.
|
|
193
192
|
|
|
194
193
|
### Load DECISIONS_CONTEXT
|
|
195
194
|
|
|
196
|
-
When the settings line says `
|
|
195
|
+
When the settings line says `CURATION=off`, set `DECISIONS_CONTEXT` to `(none)` and skip this step, locating no ledger and reading no index.
|
|
197
196
|
|
|
198
197
|
The decisions ledger belongs to the repository, not to one checkout: a linked worktree reads the main worktree's copy, a subdirectory session the repository root's. Locate it with ONE git call, run from the start directory — `WORKTREE_PATH` if provided, otherwise cwd (`devflow:worktree-support`):
|
|
199
198
|
|
|
@@ -207,11 +206,11 @@ Line 1 is the checkout's toplevel, line 2 the common git directory. A git older
|
|
|
207
206
|
2. **The toplevel** — line 1, or on an older git the line after the echoed flag.
|
|
208
207
|
3. **The start directory itself** — when the command failed or printed no absolute toplevel (outside a git repository).
|
|
209
208
|
|
|
210
|
-
This is the rule the
|
|
209
|
+
This is the rule the curation hooks apply, so you read the index the Curate agent writes.
|
|
211
210
|
|
|
212
211
|
**Step 1 — Read the pre-rendered index:**
|
|
213
212
|
|
|
214
|
-
Attempt to read `{ledger}/.devflow/
|
|
213
|
+
Attempt to read `{ledger}/.devflow/curation/index.md`.
|
|
215
214
|
|
|
216
215
|
- If the file exists and contains non-empty content: use that content as `DECISIONS_CONTEXT`.
|
|
217
216
|
- If the file is absent or empty: set `DECISIONS_CONTEXT` to `(none)`.
|
|
@@ -355,13 +354,14 @@ Output: {ANALYSIS_DIR}/bug-analysis-summary.md"
|
|
|
355
354
|
|
|
356
355
|
**Requires:** BRANCH_INFO, ANALYSIS_DIR
|
|
357
356
|
|
|
358
|
-
1.
|
|
357
|
+
1. In one Bash call run `M="$(git -C "{worktree}" rev-parse --absolute-git-dir)/devflow/bug-analysis/{branch-slug}" && mkdir -p "$M" && git -C "{worktree}" rev-parse HEAD > "$M/.last-analysis-head"`. A failed or refused write never stops the run and is not retried another way.
|
|
359
358
|
2. Report to user:
|
|
360
359
|
|
|
361
360
|
```
|
|
362
361
|
## Bug Analysis Complete
|
|
363
362
|
|
|
364
363
|
**Branch**: {branch} -> {base_branch}
|
|
364
|
+
{any base `TRACEABILITY: DEGRADED` line from Phase 1, as returned}
|
|
365
365
|
**Analysis**: {ANALYSIS_DIR}
|
|
366
366
|
|
|
367
367
|
### Risk Assessment: {risk_level}
|
|
@@ -397,11 +397,7 @@ Run `/resolve` to process and fix these findings.
|
|
|
397
397
|
│ └─ Git agent (ensure-pr-ready)
|
|
398
398
|
│
|
|
399
399
|
├─ Phase 2: Static Analysis
|
|
400
|
-
│
|
|
401
|
-
│ ├─ Step 2b: Check changed files (stop if none)
|
|
402
|
-
│ ├─ Step 2c: Tool availability check
|
|
403
|
-
│ └─ Step 2d: Tiered static analysis (semgrep → snyk → codeql)
|
|
404
|
-
│ Write static-findings.md
|
|
400
|
+
│ └─ 2a incremental range, 2b changed files (stop if none), 2c tools, 2d semgrep → snyk → codeql
|
|
405
401
|
│
|
|
406
402
|
├─ Phase 3: Context Loading
|
|
407
403
|
│ ├─ index.md (pre-rendered) → DECISIONS_CONTEXT
|
|
@@ -412,10 +408,7 @@ Run `/resolve` to process and fix these findings.
|
|
|
412
408
|
│ └─ Detect active focuses (security + functional always; integration + usability conditional)
|
|
413
409
|
│
|
|
414
410
|
├─ Phase 5: Bug Analysis (PARALLEL)
|
|
415
|
-
│
|
|
416
|
-
│ ├─ Debug agent: functional
|
|
417
|
-
│ ├─ Debug agent: integration (conditional)
|
|
418
|
-
│ └─ Debug agent: usability (conditional)
|
|
411
|
+
│ └─ Debug agent per active focus (security also reads STATIC_FINDINGS)
|
|
419
412
|
│
|
|
420
413
|
├─ Phase 6: Synthesis
|
|
421
414
|
│ └─ Synthesize agent (mode: bug-analysis)
|
|
@@ -431,16 +424,12 @@ Run `/resolve` to process and fix these findings.
|
|
|
431
424
|
|------|----------|
|
|
432
425
|
| No new commits since last analysis | Stop: "No new commits since last analysis. Use --full for a full re-analysis." |
|
|
433
426
|
| Rebase invalidates `.last-analysis-head` SHA | `git cat-file -t` check fails → fallback to full diff |
|
|
434
|
-
| Zero changed files in DIFF_RANGE | Stop: "No changes to analyze." |
|
|
427
|
+
| Zero changed files in DIFF_RANGE with exit 0 | Stop: "No changes to analyze." |
|
|
435
428
|
| Same-minute analysis collision | `mkdir` with seconds suffix (`YYYY-MM-DD_HHMMSS`) |
|
|
436
429
|
| All static tools unavailable | Warn, proceed with semantic-only analysis |
|
|
437
430
|
| `--no-static` flag | Skip Phase 2c and 2d entirely; `STATIC_FINDINGS=(none)` |
|
|
438
431
|
| `--full` flag | Bypass incremental detection (Step 2a), run full diff from base |
|
|
439
432
|
| CodeQL database creation fails | Skip CodeQL, note in output, continue with other tools |
|
|
440
|
-
| Static tool produces no findings | `STATIC_FINDINGS=(none)` — normal, proceed with semantic analysis |
|
|
441
|
-
| No plan artifact found | `PLAN_CONTEXT=(none)`, `ACCEPTANCE_RULES=(none)` — proceed without acceptance criteria |
|
|
442
|
-
| `integration` focus skipped | Not spawned when only 1 directory changed |
|
|
443
|
-
| `usability` focus skipped | Not spawned when no UI files changed |
|
|
444
433
|
|
|
445
434
|
## Resolve Compatibility
|
|
446
435
|
|