devflow-kit 3.0.1 → 3.1.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 +13 -0
- package/dist/agents/git.md +2 -2
- package/dist/cli/agents-view/index.js +1 -1
- package/dist/cli/agents-view/render.js +2 -2
- package/dist/cli/agents-view/state.js +2 -2
- package/dist/cli/agents-view/terminal.js +5 -5
- package/dist/cli/commands/agents.js +7 -6
- package/dist/cli/commands/ambient.js +1 -1
- package/dist/cli/commands/attribution-prompts.js +8 -8
- package/dist/cli/commands/capture.js +1 -1
- package/dist/cli/commands/compliance-prompts.js +8 -8
- package/dist/cli/commands/compliance.js +8 -7
- package/dist/cli/commands/flags.js +33 -31
- package/dist/cli/commands/hud.js +1 -1
- package/dist/cli/commands/init-seed.js +9 -9
- package/dist/cli/commands/init.js +34 -32
- package/dist/cli/commands/install-report.js +10 -10
- package/dist/cli/commands/learning.js +267 -129
- package/dist/cli/commands/memory.js +1 -1
- package/dist/cli/commands/proxy.js +23 -23
- package/dist/cli/commands/rules.js +6 -5
- package/dist/cli/commands/tracker-prompts.js +6 -6
- package/dist/cli/commands/tracker.js +9 -9
- package/dist/cli/commands/uninstall.js +20 -20
- package/dist/cli/flags-view/render.js +5 -5
- package/dist/cli/flags-view/state.js +9 -9
- package/dist/cli/flags-view/terminal.js +4 -4
- package/dist/cli/tui/cells.js +1 -1
- package/dist/cli/tui/terminal.js +6 -6
- package/dist/commands/dynamic-build.md +18 -4
- package/dist/commands/dynamic-plan.md +19 -5
- package/dist/commands/dynamic-profile.md +17 -3
- package/dist/commands/dynamic-tickets.md +18 -4
- package/dist/commands/release.md +15 -1
- package/dist/commands/research.md +1 -1
- package/dist/commands/resolve.md +8 -9
- package/dist/core/agent-frontmatter.js +3 -3
- package/dist/core/agent-models.js +6 -6
- package/dist/core/agent-state.js +2 -2
- package/dist/core/ansi.js +2 -2
- package/dist/core/cache.js +7 -8
- package/dist/core/codex-auth-inspect.js +4 -4
- package/dist/core/compliance-compose.js +3 -3
- package/dist/core/compliance.js +3 -4
- package/dist/core/evidence-policy.js +14 -13
- package/dist/core/external-models.js +1 -1
- package/dist/core/feature-config.js +3 -3
- package/dist/core/feature-switch.js +3 -3
- package/dist/core/flags.js +25 -25
- package/dist/core/fs-atomic.js +6 -7
- package/dist/core/learning-queue-cleanup.js +16 -80
- package/dist/core/learning-store.js +61 -0
- package/dist/core/manifest.js +5 -5
- package/dist/core/mds-variants.js +13 -13
- package/dist/core/model-discovery.js +8 -8
- package/dist/core/observations.js +17 -101
- package/dist/core/orphan-sweep.js +4 -4
- package/dist/core/plugins.js +4 -5
- package/dist/core/project-paths.js +9 -13
- package/dist/core/proxy-log.js +8 -8
- package/dist/core/proxy-state.js +3 -3
- package/dist/core/reference-sweep.js +6 -6
- package/dist/core/teammate-mode-cleanup.js +1 -1
- package/dist/core/tracker.js +14 -14
- package/dist/hud/colors.js +2 -2
- package/dist/hud/components/learning-counts.js +2 -16
- package/dist/hud/components/version-badge.js +1 -1
- package/dist/skills/git/references/pr/resolve-review-threads.md +2 -2
- package/dist/targets/claude-code/compliance-install.js +17 -15
- package/dist/targets/claude-code/hooks.js +2 -2
- package/dist/targets/claude-code/installer.js +24 -24
- package/dist/targets/claude-code/legacy.js +1 -1
- package/dist/targets/claude-code/post-install.js +7 -7
- package/dist/targets/claude-code/tracker-install.js +2 -2
- package/package.json +1 -1
- package/src/assets/agents/code.md +1 -4
- package/src/assets/agents/design.md +2 -2
- package/src/assets/agents/diagnose.md +1 -1
- package/src/assets/agents/git.mds +2 -2
- package/src/assets/agents/knowledge.md +3 -3
- package/src/assets/agents/learning.md +281 -196
- package/src/assets/agents/research.md +1 -1
- package/src/assets/agents/review.md +3 -3
- package/src/assets/agents/scrutinize.md +1 -1
- package/src/assets/agents/skim.md +1 -1
- package/src/assets/agents/triage.md +9 -9
- package/src/assets/commands/_partials/_decisions.mds +8 -3
- package/src/assets/commands/_partials/_docs_root.mds +3 -3
- package/src/assets/commands/_partials/_engine.mds +1 -1
- package/src/assets/commands/_partials/_preamble.mds +6 -2
- package/src/assets/commands/_partials/_settings.mds +2 -2
- package/src/assets/commands/dynamic-build.mds +1 -1
- package/src/assets/commands/dynamic-plan.mds +3 -3
- package/src/assets/commands/dynamic-profile.mds +1 -1
- package/src/assets/commands/dynamic-tickets.mds +2 -2
- package/src/assets/commands/release.md +15 -1
- package/src/assets/commands/research.mds +1 -1
- package/src/assets/commands/resolve.mds +8 -9
- package/src/assets/mds/git/_pr.mds +3 -3
- package/src/assets/mds/tracker/_common.mds +1 -1
- package/src/assets/mds/tracker/_github.mds +1 -1
- package/src/assets/mds/tracker/_jira.mds +1 -1
- package/src/assets/mds/tracker/_linear.mds +1 -1
- package/src/assets/mds/tracker/_mcp.mds +6 -5
- package/src/assets/scripts/hooks/assets/orchestrator-charter.md +1 -0
- package/src/assets/scripts/hooks/background-memory-update +28 -22
- package/src/assets/scripts/hooks/capture-turn +1 -17
- package/src/assets/scripts/hooks/ensure-devflow-init +1 -1
- package/src/assets/scripts/hooks/ensure-proxy +5 -6
- package/src/assets/scripts/hooks/ensure-root-gitignore +1 -1
- package/src/assets/scripts/hooks/is-hex-sha +1 -1
- package/src/assets/scripts/hooks/json-helper.cjs +348 -814
- package/src/assets/scripts/hooks/json-parse +3 -2
- package/src/assets/scripts/hooks/lib/decisions-format.cjs +205 -156
- package/src/assets/scripts/hooks/lib/learning-store.cjs +3102 -0
- package/src/assets/scripts/hooks/lib/mkdir-lock.cjs +7 -5
- package/src/assets/scripts/hooks/lib/project-paths.cjs +13 -19
- package/src/assets/scripts/hooks/lib/render-decisions.cjs +253 -226
- package/src/assets/scripts/hooks/queue-append +2 -2
- package/src/assets/scripts/hooks/resolve-project-root +3 -4
- package/src/assets/scripts/hooks/session-start-context +40 -18
- package/src/assets/scripts/lib/project-config.cjs +2 -2
- package/src/assets/scripts/pr-evidence.cjs +3 -3
- package/src/assets/scripts/redact-secrets.cjs +20 -20
- package/src/assets/scripts/release-trace.cjs +1 -1
- package/src/assets/scripts/resolve-evidence-policy.cjs +3 -3
- package/src/assets/scripts/resolve-settings.cjs +3 -3
- package/src/assets/scripts/verify-evidence.cjs +2 -2
- package/src/assets/skills/apply-decisions/SKILL.md +37 -17
- package/src/assets/skills/docs-framework/SKILL.md +2 -2
- package/src/assets/skills/feature-knowledge/SKILL.md +6 -5
- package/dist/core/observation-io.js +0 -50
- package/src/assets/scripts/hooks/decisions-usage-scan.cjs +0 -131
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
* Discovers routable external models from the routing runtime.
|
|
5
5
|
* The only impure piece of the external-model-routing design.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Pure core-layer module — no adapter imports.
|
|
8
|
+
* cwd = os.tmpdir() so discovery never assumes the devflow
|
|
9
9
|
* directory exists (the --set read path never mkdirs, so the dir may not
|
|
10
10
|
* exist; os.tmpdir() also prevents a stale subswitch.config.json in any
|
|
11
11
|
* particular working directory from causing exit 1 in the routing runtime).
|
|
12
|
-
*
|
|
12
|
+
* discoverExternalModels never throws or rejects — every
|
|
13
13
|
* failure path returns { known: false }.
|
|
14
|
-
*
|
|
14
|
+
* Real-binary tests (T1–T4) live in tests/, never in
|
|
15
15
|
* tests/integration/ which is excluded from npm test by vitest.config.ts.
|
|
16
16
|
*
|
|
17
17
|
* Branding constraint: the routing runtime package name ("subswitch") must
|
|
@@ -508,9 +508,9 @@ function buildRealSpawnAndCollect(logPath) {
|
|
|
508
508
|
* - parse failure on live payload → log reason to proxy.log.
|
|
509
509
|
* - All failures degrade to stale-cache then { known: false } — never throw.
|
|
510
510
|
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
511
|
+
* cwd = os.tmpdir() (not devflow dir, which may not exist on
|
|
512
|
+
* the cold --set path; also prevents legacy subswitch.config.json in cwd
|
|
513
|
+
* from causing exit 1 in the routing runtime).
|
|
514
514
|
*
|
|
515
515
|
* AC-C7: resolveProxyBin() is called live every invocation, never proxy.json.binPath.
|
|
516
516
|
* AC-C8: never throws or rejects.
|
|
@@ -524,7 +524,7 @@ export async function discoverExternalModels(cacheDir, logPath, deps) {
|
|
|
524
524
|
return await _discoverInternal(cacheDir, logPath, deps);
|
|
525
525
|
}
|
|
526
526
|
catch {
|
|
527
|
-
// Catch-all: discoverExternalModels MUST NOT throw
|
|
527
|
+
// Catch-all: discoverExternalModels MUST NOT throw.
|
|
528
528
|
return { known: false };
|
|
529
529
|
}
|
|
530
530
|
}
|
|
@@ -1,109 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file observations.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* The ledger entry statuses on the TypeScript side. Pure data: no I/O and no
|
|
5
|
+
* imports, so the HUD can load it without growing its import closure.
|
|
6
6
|
*/
|
|
7
7
|
/**
|
|
8
|
-
* D201:
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Re-exported through src/cli/commands/learning.ts for external consumers.
|
|
18
|
-
* Consumed by LedgerRow (this file) and LearningObservation.decisions_status (this file).
|
|
19
|
-
*/
|
|
20
|
-
export const DECISIONS_ENTRY_STATUSES = [
|
|
21
|
-
'Accepted', 'Active', 'Deprecated', 'Superseded', 'Retired',
|
|
22
|
-
];
|
|
23
|
-
/** Valid values for the decisions_status optional field — derived from DECISIONS_ENTRY_STATUSES. */
|
|
24
|
-
const VALID_DECISIONS_STATUSES = new Set(DECISIONS_ENTRY_STATUSES);
|
|
25
|
-
/**
|
|
26
|
-
* Type guard for validating raw JSON as a LearningObservation.
|
|
27
|
-
* Accepts all 4 types (v2: decision + pitfall added) and all statuses including deprecated.
|
|
28
|
-
* New optional fields (anchor_id, date, decisions_status, amendments, raw_body) are
|
|
29
|
-
* validated when present but their absence never causes rejection — backward compatible.
|
|
30
|
-
*/
|
|
31
|
-
export function isLearningObservation(obj) {
|
|
32
|
-
if (typeof obj !== 'object' || obj === null)
|
|
33
|
-
return false;
|
|
34
|
-
const o = obj;
|
|
35
|
-
// Required fields
|
|
36
|
-
if (!(typeof o.id === 'string' && o.id.length > 0))
|
|
37
|
-
return false;
|
|
38
|
-
if (!(o.type === 'workflow' || o.type === 'procedural' || o.type === 'decision' || o.type === 'pitfall'))
|
|
39
|
-
return false;
|
|
40
|
-
if (!(typeof o.pattern === 'string' && o.pattern.length > 0))
|
|
41
|
-
return false;
|
|
42
|
-
if (typeof o.confidence !== 'number')
|
|
43
|
-
return false;
|
|
44
|
-
if (typeof o.observations !== 'number')
|
|
45
|
-
return false;
|
|
46
|
-
if (typeof o.first_seen !== 'string')
|
|
47
|
-
return false;
|
|
48
|
-
if (typeof o.last_seen !== 'string')
|
|
49
|
-
return false;
|
|
50
|
-
if (!(o.status === 'observing' || o.status === 'ready' || o.status === 'created' || o.status === 'deprecated'))
|
|
51
|
-
return false;
|
|
52
|
-
if (!Array.isArray(o.evidence))
|
|
53
|
-
return false;
|
|
54
|
-
if (typeof o.details !== 'string')
|
|
55
|
-
return false;
|
|
56
|
-
// Optional ledger fields: validate type when present, reject if wrong type
|
|
57
|
-
if (o.anchor_id !== undefined && typeof o.anchor_id !== 'string')
|
|
58
|
-
return false;
|
|
59
|
-
if (o.date !== undefined && typeof o.date !== 'string')
|
|
60
|
-
return false;
|
|
61
|
-
if (o.decisions_status !== undefined && !VALID_DECISIONS_STATUSES.has(o.decisions_status))
|
|
62
|
-
return false;
|
|
63
|
-
if (o.amendments !== undefined) {
|
|
64
|
-
if (!Array.isArray(o.amendments))
|
|
65
|
-
return false;
|
|
66
|
-
for (const a of o.amendments) {
|
|
67
|
-
if (typeof a !== 'object' || a === null)
|
|
68
|
-
return false;
|
|
69
|
-
const am = a;
|
|
70
|
-
if (typeof am.date !== 'string' || typeof am.note !== 'string')
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
if (o.raw_body !== undefined && typeof o.raw_body !== 'string')
|
|
75
|
-
return false;
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* Parse a JSONL learning log into typed observations.
|
|
80
|
-
* Skips empty and malformed lines.
|
|
81
|
-
*/
|
|
82
|
-
export function parseLearningLog(logContent) {
|
|
83
|
-
const observations = [];
|
|
84
|
-
for (const line of logContent.split('\n')) {
|
|
85
|
-
const trimmed = line.trim();
|
|
86
|
-
if (!trimmed)
|
|
87
|
-
continue;
|
|
88
|
-
try {
|
|
89
|
-
const parsed = JSON.parse(trimmed);
|
|
90
|
-
if (isLearningObservation(parsed)) {
|
|
91
|
-
observations.push(parsed);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
// Skip malformed lines
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return observations;
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Parse a JSONL log and return valid observations plus the count of invalid entries.
|
|
102
|
-
* Centralises the raw-line-count + parse pattern used by --status, --list, and --purge.
|
|
8
|
+
* D201: a ledger entry's `decisions_status` takes one of DECISIONS_ENTRY_STATUSES.
|
|
9
|
+
* `Accepted` (decisions) and `Active` (pitfalls) are active and render; the
|
|
10
|
+
* INACTIVE_DECISIONS_STATUSES stay in the ledger and leave the rendered files and
|
|
11
|
+
* the HUD counts; an absent or unknown status counts as active. These lists
|
|
12
|
+
* mirror ACTIVE_STATUSES / INACTIVE_STATUSES in
|
|
13
|
+
* src/assets/scripts/hooks/lib/learning-store.cjs, which the hooks and the
|
|
14
|
+
* renderer use, and the parity test in tests/decisions/learning-store.test.ts
|
|
15
|
+
* pins the two equal. Reason: the renderer, the HUD and this module each kept a
|
|
16
|
+
* list of their own, so a status added to one counted as active in the others.
|
|
103
17
|
*/
|
|
104
|
-
export
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
18
|
+
export const INACTIVE_DECISIONS_STATUSES = ['Encoded', 'Superseded', 'Retired', 'Deprecated'];
|
|
19
|
+
export const DECISIONS_ENTRY_STATUSES = ['Accepted', 'Active', ...INACTIVE_DECISIONS_STATUSES];
|
|
20
|
+
const INACTIVE_DECISIONS_STATUS_SET = new Set(INACTIVE_DECISIONS_STATUSES);
|
|
21
|
+
/** True unless `status` is one of INACTIVE_DECISIONS_STATUSES (D201). */
|
|
22
|
+
export function isActiveDecisionsStatus(status) {
|
|
23
|
+
return !status || !INACTIVE_DECISIONS_STATUS_SET.has(status);
|
|
108
24
|
}
|
|
109
25
|
//# sourceMappingURL=observations.js.map
|
|
@@ -61,8 +61,8 @@ export function mdEntryName(entry) {
|
|
|
61
61
|
*
|
|
62
62
|
* Per-item failure isolation: both the outer readdir and the inner rm are
|
|
63
63
|
* independently try/caught — a missing directory is a no-op and a failed
|
|
64
|
-
* individual removal is recorded in `failed` without aborting the sweep
|
|
65
|
-
* Never writes, only removes
|
|
64
|
+
* individual removal is recorded in `failed` without aborting the sweep.
|
|
65
|
+
* Never writes, only removes — no delete-then-write window.
|
|
66
66
|
*/
|
|
67
67
|
export async function sweepOrphanedAssets(dir, knownNames, extractRegistryName) {
|
|
68
68
|
const removed = [];
|
|
@@ -81,12 +81,12 @@ export async function sweepOrphanedAssets(dir, knownNames, extractRegistryName)
|
|
|
81
81
|
removed.push(registryName);
|
|
82
82
|
}
|
|
83
83
|
catch (err) {
|
|
84
|
-
failed.push({ name: registryName, error: err }); /* per-item isolation
|
|
84
|
+
failed.push({ name: registryName, error: err }); /* per-item isolation */
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
catch { /* directory absent or unreadable — not an error
|
|
89
|
+
catch { /* directory absent or unreadable — not an error */ }
|
|
90
90
|
return { scanned, removed, failed };
|
|
91
91
|
}
|
|
92
92
|
//# sourceMappingURL=orphan-sweep.js.map
|
package/dist/core/plugins.js
CHANGED
|
@@ -465,7 +465,7 @@ export const DELETED_PLUGIN_NAMES = [
|
|
|
465
465
|
* - uninstall.ts sweepDevflowNamespaces: union into knownNames to spare devflow:compliance
|
|
466
466
|
* from the post-selective-uninstall sweep (nothing converges after selective uninstall)
|
|
467
467
|
* - skills.ts: union into allSkills for shadow/unshadow/list
|
|
468
|
-
* - tests: independent literal ['compliance'] (avoids EXCLUDED-as-oracle trap
|
|
468
|
+
* - tests: independent literal ['compliance'] (avoids EXCLUDED-as-oracle trap)
|
|
469
469
|
*
|
|
470
470
|
* D-FO-1: FEATURE_OWNED_SKILLS must be disjoint from getAllSkillNames()
|
|
471
471
|
* (guarded by plugins.test.ts FEATURE_OWNED constants describe block).
|
|
@@ -478,7 +478,7 @@ export const FEATURE_OWNED_SKILLS = ['compliance'];
|
|
|
478
478
|
*
|
|
479
479
|
* Used by:
|
|
480
480
|
* - rules.ts: union into allRules for shadow/unshadow/list
|
|
481
|
-
* - tests: independent literal ['compliance'] (avoids EXCLUDED-as-oracle trap
|
|
481
|
+
* - tests: independent literal ['compliance'] (avoids EXCLUDED-as-oracle trap)
|
|
482
482
|
*
|
|
483
483
|
* D-FO-2: FEATURE_OWNED_RULES must be disjoint from getAllRuleNames()
|
|
484
484
|
* (guarded by plugins.test.ts FEATURE_OWNED constants describe block).
|
|
@@ -511,7 +511,7 @@ export const PRESENCE_GATED_SKILLS = [
|
|
|
511
511
|
* The classified exception to the closure guard — declared HERE, at the
|
|
512
512
|
* declaration site of the field it exempts, and imported by the guard.
|
|
513
513
|
*
|
|
514
|
-
* D-TEMPLATE-EXCEPTION (
|
|
514
|
+
* D-TEMPLATE-EXCEPTION (one authority for a prohibition and its
|
|
515
515
|
* exemptions). Every other templated reference in the corpus carries a literal
|
|
516
516
|
* prefix and resolves through it — `devflow:research-{RESEARCH_TYPE}` resolves
|
|
517
517
|
* because five in-scope skills start with `research-`. The Review focus skill is
|
|
@@ -745,8 +745,7 @@ export function buildFullSkillsMap() {
|
|
|
745
745
|
* add X, not a statement that X is the whole selection, so it may never remove
|
|
746
746
|
* what another plugin contributed (AC-22). {@link FEATURE_OWNED_SKILLS} is
|
|
747
747
|
* subtracted unconditionally: those install and uninstall with their feature,
|
|
748
|
-
* and sweeping them here would delete an artifact this code does not own
|
|
749
|
-
* (applies ADR-024).
|
|
748
|
+
* and sweeping them here would delete an artifact this code does not own.
|
|
750
749
|
*
|
|
751
750
|
* A shadow is NEVER removed, whatever the selection — `~/.devflow/skills/` is
|
|
752
751
|
* user content. One that falls outside the install set simply applies to
|
|
@@ -46,10 +46,14 @@ export function getFeatureConfigPath(projectRoot) {
|
|
|
46
46
|
export function getLearningPendingTurnsPath(projectRoot) {
|
|
47
47
|
return path.join(projectRoot, '.devflow', 'learning', '.pending-turns.jsonl');
|
|
48
48
|
}
|
|
49
|
-
/** .devflow/learning/.pending-turns.processing —
|
|
49
|
+
/** .devflow/learning/.pending-turns.processing — the claimed batch a Learning run holds while it processes it */
|
|
50
50
|
export function getLearningPendingTurnsProcessingPath(projectRoot) {
|
|
51
51
|
return path.join(projectRoot, '.devflow', 'learning', '.pending-turns.processing');
|
|
52
52
|
}
|
|
53
|
+
/** .devflow/learning/.pending-turns.owner — the token of the run that holds the claim (learning-store.cjs) */
|
|
54
|
+
export function getLearningClaimOwnerPath(projectRoot) {
|
|
55
|
+
return path.join(projectRoot, '.devflow', 'learning', '.pending-turns.owner');
|
|
56
|
+
}
|
|
53
57
|
// ---------------------------------------------------------------------------
|
|
54
58
|
// Learning content files
|
|
55
59
|
// ---------------------------------------------------------------------------
|
|
@@ -77,26 +81,18 @@ export function getDecisionsLogPath(projectRoot) {
|
|
|
77
81
|
export function getDecisionsArchivePath(projectRoot) {
|
|
78
82
|
return path.join(projectRoot, '.devflow', 'learning', 'decisions-log.archive.jsonl');
|
|
79
83
|
}
|
|
84
|
+
/** .devflow/learning/decisions-history.jsonl — prior content versions of rewritten entries (learning-store.cjs) */
|
|
85
|
+
export function getDecisionsHistoryPath(projectRoot) {
|
|
86
|
+
return path.join(projectRoot, '.devflow', 'learning', 'decisions-history.jsonl');
|
|
87
|
+
}
|
|
80
88
|
/** .devflow/learning/.decisions.lock — mkdir-based lock directory */
|
|
81
89
|
export function getDecisionsLockDir(projectRoot) {
|
|
82
90
|
return path.join(projectRoot, '.devflow', 'learning', '.decisions.lock');
|
|
83
91
|
}
|
|
84
|
-
/** .devflow/learning/.decisions-usage.json */
|
|
85
|
-
export function getDecisionsUsagePath(projectRoot) {
|
|
86
|
-
return path.join(projectRoot, '.devflow', 'learning', '.decisions-usage.json');
|
|
87
|
-
}
|
|
88
|
-
/** .devflow/learning/.decisions-usage.lock/ — mkdir-based lock directory for usage file */
|
|
89
|
-
export function getDecisionsUsageLockDir(projectRoot) {
|
|
90
|
-
return path.join(projectRoot, '.devflow', 'learning', '.decisions-usage.lock');
|
|
91
|
-
}
|
|
92
92
|
/** .devflow/learning/index.md — pre-rendered compact index written by render-decisions.cjs */
|
|
93
93
|
export function getDecisionsIndexPath(projectRoot) {
|
|
94
94
|
return path.join(projectRoot, '.devflow', 'learning', 'index.md');
|
|
95
95
|
}
|
|
96
|
-
/** .devflow/learning/.observations.lock — mkdir-based lock directory for observation log writes */
|
|
97
|
-
export function getObservationsLockDir(projectRoot) {
|
|
98
|
-
return path.join(projectRoot, '.devflow', 'learning', '.observations.lock');
|
|
99
|
-
}
|
|
100
96
|
// ---------------------------------------------------------------------------
|
|
101
97
|
// Memory / working-memory files
|
|
102
98
|
// ---------------------------------------------------------------------------
|
package/dist/core/proxy-log.js
CHANGED
|
@@ -10,8 +10,8 @@ import * as path from 'path';
|
|
|
10
10
|
* - openProxyLog — 0700-parent + 0600-file open with best-effort chmod (SEC-2)
|
|
11
11
|
* - rotateProxyLogIfLarge — pre-spawn-only 2MB→1MB rotation that preserves 0600 mode
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Every failure path is non-fatal (wrapped in try/catch); one bad
|
|
14
|
+
* chmod or rotation error must never abort the enable flow.
|
|
15
15
|
*/
|
|
16
16
|
/** 2 MB max log size before rotation (mirrors ensure-proxy _LOG_MAX_BYTES). */
|
|
17
17
|
export const PROXY_LOG_MAX_BYTES = 2_097_152;
|
|
@@ -28,7 +28,7 @@ export const PROXY_LOG_TAIL_BYTES = 1_048_576;
|
|
|
28
28
|
* Call sites compose on top of this result to add process-specific vars
|
|
29
29
|
* (e.g. SUBSWITCH_CONFIG for the relay spawn and doctor spawn).
|
|
30
30
|
*
|
|
31
|
-
*
|
|
31
|
+
* On the paths devflow
|
|
32
32
|
* invokes (serve, doctor, models) the routing runtime reads ANTHROPIC_API_KEY,
|
|
33
33
|
* FORCE_COLOR, NO_COLOR (dist/tty.js, presence semantics), SUBSWITCH_CONFIG and
|
|
34
34
|
* XDG_CONFIG_HOME (user-config lookup, reached only on an implicit config load —
|
|
@@ -42,8 +42,8 @@ export const PROXY_LOG_TAIL_BYTES = 1_048_576;
|
|
|
42
42
|
* NODE_EXTRA_CA_CERTS is included so corporate-TLS deployments can supply a CA
|
|
43
43
|
* bundle; it is omitted when unset (the absent-→-omit loop handles this).
|
|
44
44
|
* NODE_OPTIONS is deliberately excluded: it permits arbitrary code execution via
|
|
45
|
-
* --require/--import. Mirrored in the ensure-proxy bash hook's _RELAY_ENV array
|
|
46
|
-
*
|
|
45
|
+
* --require/--import. Mirrored in the ensure-proxy bash hook's _RELAY_ENV array;
|
|
46
|
+
* both allowlists must be kept in sync.
|
|
47
47
|
*/
|
|
48
48
|
export function scrubChildEnv() {
|
|
49
49
|
const POSIX_ALLOWLIST = ['PATH', 'HOME', 'TMPDIR', 'LANG', 'LC_ALL', 'NODE_EXTRA_CA_CERTS'];
|
|
@@ -71,7 +71,7 @@ export function scrubChildEnv() {
|
|
|
71
71
|
* 3. best-effort fs.chmod(logPath, 0o600) — widens a pre-existing file
|
|
72
72
|
* that was created before this guard existed (e.g. mode 0644 on disk).
|
|
73
73
|
* Non-fatal: a chmod failure (EPERM, ENOENT race) must never prevent the
|
|
74
|
-
* handle from being returned
|
|
74
|
+
* handle from being returned.
|
|
75
75
|
* Follows the SEC-1 precedent in src/core/fs-atomic.ts (commit 5755d56):
|
|
76
76
|
* chmod in try/catch, never fatal, with a comment citing the rationale.
|
|
77
77
|
*
|
|
@@ -88,7 +88,7 @@ export async function openProxyLog(logPath) {
|
|
|
88
88
|
const handle = await fs.open(logPath, 'a', 0o600);
|
|
89
89
|
// Step 3: best-effort chmod for a pre-existing file that has a wider mode.
|
|
90
90
|
// Non-fatal: chmod failure must not prevent the handle from being returned.
|
|
91
|
-
//
|
|
91
|
+
// Failure isolation — one bad chmod must never abort the enable flow.
|
|
92
92
|
try {
|
|
93
93
|
await fs.chmod(logPath, 0o600);
|
|
94
94
|
}
|
|
@@ -120,7 +120,7 @@ export async function openProxyLog(logPath) {
|
|
|
120
120
|
*
|
|
121
121
|
* Non-fatal: rotation failure (disk full, EPERM, read error) leaves the
|
|
122
122
|
* original log untouched and the enable proceeds with an oversized log.
|
|
123
|
-
*
|
|
123
|
+
* One rotation error must never abort the enable flow.
|
|
124
124
|
*
|
|
125
125
|
* @param logPath - Absolute path to proxy.log.
|
|
126
126
|
*/
|
package/dist/core/proxy-state.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Proxy state persistence and routing config helpers for the Devflow external
|
|
3
3
|
* model routing feature.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Pure core-layer module — no Claude Code adapter concerns.
|
|
6
|
+
* Never call process.exit() inside finally-guarded scopes; all
|
|
7
7
|
* fallible operations return Result instead of throwing.
|
|
8
8
|
*
|
|
9
9
|
* State file: ~/.devflow/proxy.json
|
|
@@ -155,7 +155,7 @@ const ROUTING_CONFIG_REJECTED_SUBKEYS = {
|
|
|
155
155
|
*
|
|
156
156
|
* If `existingContent` is missing or malformed, falls back to a port-only config.
|
|
157
157
|
*
|
|
158
|
-
*
|
|
158
|
+
* Pure core-layer function — no I/O.
|
|
159
159
|
* @D-EFR-4: see note above for the strict top-key constraint.
|
|
160
160
|
*/
|
|
161
161
|
export function buildRoutingConfigJson(port, existingContent) {
|
|
@@ -7,14 +7,14 @@ import * as path from 'path';
|
|
|
7
7
|
*
|
|
8
8
|
* Sibling of {@link sweepOrphanedAssets} in orphan-sweep.ts and deliberately the same
|
|
9
9
|
* {@link SweepResult} shape — `scanned` is the non-vacuity counter, removals and
|
|
10
|
-
* per-item failures are reported rather than thrown
|
|
10
|
+
* per-item failures are reported rather than thrown.
|
|
11
11
|
*
|
|
12
12
|
* What is genuinely new is the KEY. `sweepOrphanedAssets` keys a flat directory by
|
|
13
13
|
* registry name through `mdEntryName`, which cannot express `tracker/{provider}/{op}.md`:
|
|
14
14
|
* two providers may legitimately both carry a `comment.md`, so the registry name has to
|
|
15
15
|
* be the relative PATH, and the walk has to descend.
|
|
16
16
|
*
|
|
17
|
-
* Never writes, only removes
|
|
17
|
+
* Never writes, only removes — no delete-then-write window.
|
|
18
18
|
*/
|
|
19
19
|
/**
|
|
20
20
|
* Descent bound for every walk over the generated reference tree — this sweep and the
|
|
@@ -32,7 +32,7 @@ import * as path from 'path';
|
|
|
32
32
|
* The two walkers answer a breach differently by design, and both answer out loud: the
|
|
33
33
|
* build throws (a generated tree that deep is a build bug, and dist/ is still the
|
|
34
34
|
* build's own to fail), while this sweep records the unvisited subtree in `failed`
|
|
35
|
-
* (
|
|
35
|
+
* (an install is not abandoned over one subtree). Neither returns
|
|
36
36
|
* quietly: a subtree the walk never entered must not be summarised as converged.
|
|
37
37
|
*/
|
|
38
38
|
export const MAX_REFERENCE_SWEEP_DEPTH = 8;
|
|
@@ -76,7 +76,7 @@ async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, acc)
|
|
|
76
76
|
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
77
77
|
}
|
|
78
78
|
catch {
|
|
79
|
-
return; /* absent or unreadable — not an error
|
|
79
|
+
return; /* absent or unreadable — not an error */
|
|
80
80
|
}
|
|
81
81
|
for (const entry of entries) {
|
|
82
82
|
const relPath = prefix === '' ? entry.name : `${prefix}/${entry.name}`;
|
|
@@ -96,7 +96,7 @@ async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, acc)
|
|
|
96
96
|
acc.removed.push(relPath);
|
|
97
97
|
}
|
|
98
98
|
catch (err) {
|
|
99
|
-
acc.failed.push({ name: relPath, error: err }); /* per-item isolation
|
|
99
|
+
acc.failed.push({ name: relPath, error: err }); /* per-item isolation */
|
|
100
100
|
}
|
|
101
101
|
continue;
|
|
102
102
|
}
|
|
@@ -108,7 +108,7 @@ async function sweepDirectory(dir, prefix, depth, known, knownDirPrefixes, acc)
|
|
|
108
108
|
acc.removed.push(relPath);
|
|
109
109
|
}
|
|
110
110
|
catch (err) {
|
|
111
|
-
acc.failed.push({ name: relPath, error: err }); /* per-item isolation
|
|
111
|
+
acc.failed.push({ name: relPath, error: err }); /* per-item isolation */
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Tolerant: malformed JSON is returned unchanged (no-op). Non-object roots
|
|
11
11
|
* (null, arrays, primitives) are treated as a no-op — only object roots can
|
|
12
|
-
* carry the key (
|
|
12
|
+
* carry the key (a TypeError here would escape the function
|
|
13
13
|
* and surface as an unhandled error during uninstall).
|
|
14
14
|
*/
|
|
15
15
|
export function stripDevflowTeammateModeFromJson(settingsJson) {
|
package/dist/core/tracker.js
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
* migrateLegacyTrackerConventions) owns the ~/.devflow tracker files.
|
|
10
10
|
* It sits here rather than in a target adapter because ~/.devflow is
|
|
11
11
|
* devflow-global, not Claude-Code-specific — the same reason manifest.ts's
|
|
12
|
-
* read/write live in src/core
|
|
12
|
+
* read/write live in src/core/.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Nothing here calls process.exit() and nothing throws; every
|
|
15
15
|
* fallible path returns a Result, so callers own their own error rendering
|
|
16
16
|
* and a try/finally in a caller is never skipped.
|
|
17
17
|
*
|
|
@@ -31,7 +31,7 @@ import * as path from 'path';
|
|
|
31
31
|
/**
|
|
32
32
|
* Canonical issue-tracker provider registry.
|
|
33
33
|
*
|
|
34
|
-
* D-TRACKER-ONE-DOMAIN
|
|
34
|
+
* D-TRACKER-ONE-DOMAIN: this table is the SINGLE authority on the closed
|
|
35
35
|
* provider set, and `TrackerProvider` below is a projection of it. A provider
|
|
36
36
|
* therefore exists for the type system exactly when it has a row here: there is no
|
|
37
37
|
* hand-listed union that can admit an id `parseTrackerId` rejects and the wizard
|
|
@@ -63,7 +63,7 @@ export const DEFAULT_TRACKER_PROVIDER = 'github';
|
|
|
63
63
|
// Artifact basenames — one spelling for every TypeScript reader
|
|
64
64
|
//
|
|
65
65
|
// NOT the only spelling in the repository, and a rename that assumes it is will
|
|
66
|
-
// miss the places these names are hardcoded
|
|
66
|
+
// miss the places these names are hardcoded: the SessionStart hook's
|
|
67
67
|
// Section 3 (shell) and the Tracker agent's prompt (prose), neither of which can
|
|
68
68
|
// import from here. Each is cross-pinned against these constants by tests —
|
|
69
69
|
// shell-hooks-tracker, tracker-agent, uninstall-logic and core/tracker — so the
|
|
@@ -117,7 +117,7 @@ export const TRACKER_CLAIM_FILE = '.tracker.processing';
|
|
|
117
117
|
* no user-authored content (it is a scrubbed, unplaced copy of what the agent
|
|
118
118
|
* was about to write), so it is an install artifact, never user content.
|
|
119
119
|
*
|
|
120
|
-
* Spelled twice for the reason the basenames above are
|
|
120
|
+
* Spelled twice for the reason the basenames above are: the agent's
|
|
121
121
|
* prompt cannot import from here, so the mktemp template is also a literal in
|
|
122
122
|
* src/assets/agents/tracker.md, and tests/core/tracker.test.ts pins the two
|
|
123
123
|
* spellings together.
|
|
@@ -142,7 +142,7 @@ export const TRACKER_ATTEMPTS_NAMES = TRACKER_PROVIDER_IDS.map(id => trackerAtte
|
|
|
142
142
|
* How many background inference attempts a machine gets before the SessionStart
|
|
143
143
|
* hook stops emitting the setup directive.
|
|
144
144
|
*
|
|
145
|
-
* Spelled twice for the reason the basenames above are
|
|
145
|
+
* Spelled twice for the reason the basenames above are: the hook is the
|
|
146
146
|
* enforcer and cannot import from here, so `TRACKER_ATTEMPTS_MAX=5` is also a
|
|
147
147
|
* literal in src/assets/scripts/hooks/session-start-context. This constant is the
|
|
148
148
|
* number `devflow tracker --status` quotes back when it re-arms the counter, and
|
|
@@ -187,7 +187,7 @@ export function isTrackerProvider(value) {
|
|
|
187
187
|
* `parseTrackerId`'s error text and by `devflow tracker --status`.
|
|
188
188
|
*
|
|
189
189
|
* Takes `unknown`, and a non-string renders as its TYPE: the module's
|
|
190
|
-
* never-throws contract
|
|
190
|
+
* never-throws contract has to hold for what reaches this sink, not
|
|
191
191
|
* only for what the signature says does — `devflow tracker --status` reads
|
|
192
192
|
* a conventions file's hand-editable frontmatter, and a caller-side guard is one edit
|
|
193
193
|
* from being gone. Naming the type also keeps the render total, where `String()`
|
|
@@ -242,12 +242,12 @@ export function parseTrackerId(input) {
|
|
|
242
242
|
* Tolerant sink normaliser for a raw `manifest.features.tracker` value.
|
|
243
243
|
*
|
|
244
244
|
* Absent, null, malformed, a bare string, or an unknown provider → the default
|
|
245
|
-
* `{provider:'github'}` (
|
|
245
|
+
* `{provider:'github'}` (self-heal). Drop-not-error: a manifest
|
|
246
246
|
* written by a newer devflow, or hand-edited, degrades to what this build
|
|
247
247
|
* understands instead of failing the read.
|
|
248
248
|
*
|
|
249
249
|
* D-TRACKER-SELF-HEAL [DR-26]: self-healing here is SILENT and emits no
|
|
250
|
-
* DEGRADED — that is the correct
|
|
250
|
+
* DEGRADED — that is the correct self-heal behaviour, and it is a different
|
|
251
251
|
* condition from a per-repo config value outside the domain (which does emit
|
|
252
252
|
* `unknown tracker provider`). The two must not be conflated.
|
|
253
253
|
*/
|
|
@@ -317,8 +317,8 @@ export function parseTrackerFrontmatter(head) {
|
|
|
317
317
|
/**
|
|
318
318
|
* How many leading BYTES of a conventions file any reader takes — the bound the
|
|
319
319
|
* Tracker agent writes to and the Git agent loads. A line cap alone bounds the
|
|
320
|
-
* SCAN, not the read: the file's size is not devflow's to assume
|
|
321
|
-
* a bound is only real at the sink).
|
|
320
|
+
* SCAN, not the read: the file's size is not devflow's to assume
|
|
321
|
+
* (a bound is only real at the sink).
|
|
322
322
|
*/
|
|
323
323
|
export const TRACKER_CONVENTIONS_READ_BYTES = 8000;
|
|
324
324
|
/**
|
|
@@ -327,7 +327,7 @@ export const TRACKER_CONVENTIONS_READ_BYTES = 8000;
|
|
|
327
327
|
* `undefined` for an absent, unreadable or non-regular path — a FIFO or a device
|
|
328
328
|
* is refused before it is opened, so a hostile entry can never block a read.
|
|
329
329
|
* Follows a symlink to read what it names (a reader cares what the conventions
|
|
330
|
-
* SAY); it never moves or writes through one. Never throws
|
|
330
|
+
* SAY); it never moves or writes through one. Never throws.
|
|
331
331
|
*/
|
|
332
332
|
export async function readBoundedHead(filePath, limit) {
|
|
333
333
|
let handle;
|
|
@@ -360,7 +360,7 @@ export async function readBoundedHead(filePath, limit) {
|
|
|
360
360
|
* select a provider the machine never did, and its counter is the one a user in
|
|
361
361
|
* that repository is capped on.
|
|
362
362
|
*
|
|
363
|
-
* Idempotent when a counter is absent; never throws
|
|
363
|
+
* Idempotent when a counter is absent; never throws. `fs.rm` with
|
|
364
364
|
* `force` treats an absent file — and an absent parent directory — as success.
|
|
365
365
|
*/
|
|
366
366
|
export async function rearmTrackerInference(devflowDir) {
|
|
@@ -385,7 +385,7 @@ export async function rearmTrackerInference(devflowDir) {
|
|
|
385
385
|
* bare presence marker, so the hook never has to open the manifest to learn which
|
|
386
386
|
* provider it is gating.
|
|
387
387
|
*
|
|
388
|
-
* Converges unconditionally in both directions
|
|
388
|
+
* Converges unconditionally in both directions: a provider
|
|
389
389
|
* flipped back to github removes the sentinel in the same call shape that wrote
|
|
390
390
|
* it, so there is no "enable wrote it, disable forgot it" asymmetry.
|
|
391
391
|
*/
|
package/dist/hud/colors.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ANSI color helpers — re-exported from src/core/ansi.ts.
|
|
3
3
|
*
|
|
4
|
-
* The canonical implementation lives in src/core/ansi.ts (agent-neutral home
|
|
5
|
-
*
|
|
4
|
+
* The canonical implementation lives in src/core/ansi.ts (agent-neutral home).
|
|
5
|
+
* This file is a re-export barrel so all existing HUD
|
|
6
6
|
* component call sites continue to resolve `../colors.js` without change.
|
|
7
7
|
*/
|
|
8
8
|
export { bold, dim, red, green, yellow, blue, magenta, cyan, gray, white, orange, brightRed, boldRed, bgGreen, bgYellow, bgRed, inverse, truncate, stripAnsi, } from '../core/ansi.js';
|
|
@@ -2,18 +2,9 @@ import * as fs from 'node:fs';
|
|
|
2
2
|
import { dim } from '../colors.js';
|
|
3
3
|
import { getDecisionsLedgerPath } from '../../core/project-paths.js';
|
|
4
4
|
import { getLedgerRoot } from '../../core/ledger-root.js';
|
|
5
|
+
import { isActiveDecisionsStatus } from '../../core/observations.js';
|
|
5
6
|
/** The HUD's per-git-command budget (src/hud/git.ts GIT_TIMEOUT). */
|
|
6
7
|
const LEDGER_ROOT_TIMEOUT_MS = 1000;
|
|
7
|
-
/**
|
|
8
|
-
* @devflow-design-decision D309
|
|
9
|
-
* Counts come from decisions-ledger.jsonl (the render source of truth), NOT
|
|
10
|
-
* the rendered decisions.md/pitfalls.md, so the HUD never couples to markdown
|
|
11
|
-
* format. Active-row semantics mirror render-decisions.cjs exactly: a row
|
|
12
|
-
* counts when anchor_id is set and decisions_status is absent or outside
|
|
13
|
-
* INACTIVE_STATUSES — so the numbers always equal the entries visible in the
|
|
14
|
-
* rendered files.
|
|
15
|
-
*/
|
|
16
|
-
const INACTIVE_STATUSES = new Set(['Deprecated', 'Superseded', 'Retired']);
|
|
17
8
|
function isLedgerCountRow(val) {
|
|
18
9
|
if (typeof val !== 'object' || val === null)
|
|
19
10
|
return false;
|
|
@@ -24,11 +15,6 @@ function isLedgerCountRow(val) {
|
|
|
24
15
|
return false;
|
|
25
16
|
return o.decisions_status === undefined || typeof o.decisions_status === 'string';
|
|
26
17
|
}
|
|
27
|
-
function isActive(row) {
|
|
28
|
-
if (!row.decisions_status)
|
|
29
|
-
return true;
|
|
30
|
-
return !INACTIVE_STATUSES.has(row.decisions_status);
|
|
31
|
-
}
|
|
32
18
|
/**
|
|
33
19
|
* Read .devflow/learning/decisions-ledger.jsonl and count active anchored
|
|
34
20
|
* rows by type. Returns null if the ledger is missing or holds no valid rows
|
|
@@ -59,7 +45,7 @@ export function gatherLearningCounts(cwd) {
|
|
|
59
45
|
if (!isLedgerCountRow(parsed))
|
|
60
46
|
continue;
|
|
61
47
|
parsedAny = true;
|
|
62
|
-
if (!
|
|
48
|
+
if (!isActiveDecisionsStatus(parsed.decisions_status))
|
|
63
49
|
continue;
|
|
64
50
|
if (parsed.type === 'decision')
|
|
65
51
|
counts.decisions++;
|
|
@@ -70,7 +70,7 @@ export default async function versionBadge(ctx) {
|
|
|
70
70
|
const current = getCurrentVersion(ctx.devflowDir);
|
|
71
71
|
if (!current)
|
|
72
72
|
return null;
|
|
73
|
-
const cacheDir = hudCacheDir(ctx.devflowDir); // authoritative path from cache.ts
|
|
73
|
+
const cacheDir = hudCacheDir(ctx.devflowDir); // authoritative path from cache.ts
|
|
74
74
|
// Cache only the npm registry result (expensive); current is always live
|
|
75
75
|
let info = readCache(cacheDir, VERSION_CACHE_KEY, validateVersionInfo);
|
|
76
76
|
if (!info) {
|
|
@@ -9,7 +9,7 @@ Load for `resolve-review-threads` under every tracker provider.
|
|
|
9
9
|
Each `THREAD_MAP` entry carries one verdict:
|
|
10
10
|
- `FIXED` — issue addressed
|
|
11
11
|
- `FALSE_POSITIVE` — not a real issue; requires grep/file:line citation as evidence
|
|
12
|
-
- `BY_DESIGN` — intentional; requires
|
|
12
|
+
- `BY_DESIGN` — intentional; requires a recorded decision, stated in words, or code citation as evidence
|
|
13
13
|
- `ESCALATED` — requires human review
|
|
14
14
|
|
|
15
15
|
(The resolution gate these verdicts feed — D9 — is stated in the agent's own section.)
|
|
@@ -28,7 +28,7 @@ unexplained unresolved threads.
|
|
|
28
28
|
- **FALSE_POSITIVE**: `After investigation, this appears to be a false positive: {evidence}. No code change needed.`
|
|
29
29
|
- **BY_DESIGN**: `This is intentional: {evidence}. No code change needed.`
|
|
30
30
|
- **ESCALATED**: `This thread has been escalated for human review and recorded in the resolution summary.`
|
|
31
|
-
- Reply bodies MUST NOT contain verbatim content from the external thread body — cite only internal evidence (commit SHAs, file:line from this codebase
|
|
31
|
+
- Reply bodies MUST NOT contain verbatim content from the external thread body — cite only internal evidence (commit SHAs, file:line from this codebase)
|
|
32
32
|
2. Write reply to `$DEVFLOW_BODY_RAW`; apply the Comment-sink scrub (D11) — non-zero exit → DEGRADED for that thread, continue per D4. Post reply via `addPullRequestReviewThreadReply` GraphQL mutation with `-F body=@"$DEVFLOW_BODY"` (file-ref form).
|
|
33
33
|
|
|
34
34
|
(Step 3, the D9 gate, is stated in the agent's own section.)
|