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
|
@@ -14,7 +14,7 @@ import { loadProjectConfigLib } from './evidence-policy.js';
|
|
|
14
14
|
* carrying it would leave a `learning: false` in the file that no longer does
|
|
15
15
|
* what it says. `decisions` is the pre-rename spelling of `learning`;
|
|
16
16
|
* `autoCommit` is inert. `features` is deliberately NOT here — it is a live key,
|
|
17
|
-
* carried like any other unmanaged key
|
|
17
|
+
* carried like any other unmanaged key.
|
|
18
18
|
*/
|
|
19
19
|
const RETIRED_CONFIG_KEYS = new Set([
|
|
20
20
|
'memory', 'learning', 'knowledge', 'decisions', 'autoCommit',
|
|
@@ -77,7 +77,7 @@ function coerceConfig(parsed) {
|
|
|
77
77
|
if (!isJsonObject(parsed))
|
|
78
78
|
return null;
|
|
79
79
|
const p = parsed;
|
|
80
|
-
// Coerce reviewPublication: any invalid or absent value → 'auto' (self-heal
|
|
80
|
+
// Coerce reviewPublication: any invalid or absent value → 'auto' (self-heal).
|
|
81
81
|
const rp = p.reviewPublication;
|
|
82
82
|
const reviewPublication = rp === 'auto' || rp === 'full' || rp === 'off' ? rp : 'auto';
|
|
83
83
|
// The per-repo tracker override is carried through VERBATIM — never coerced,
|
|
@@ -185,7 +185,7 @@ async function writeConfigBody(projectRoot, body) {
|
|
|
185
185
|
* Merge devflow's managed keys over the config body the file already holds.
|
|
186
186
|
* Pure — returns a new object and never mutates `existing`.
|
|
187
187
|
*
|
|
188
|
-
* D-CONFIG-PRESERVE-UNMANAGED
|
|
188
|
+
* D-CONFIG-PRESERVE-UNMANAGED: `.devflow/config.json` is a
|
|
189
189
|
* user-editable file that devflow only PARTLY owns. The managed keys come from
|
|
190
190
|
* `managed`; every other key comes from the file, verbatim and by key presence
|
|
191
191
|
* — the hand-written per-repo `tracker` override (whose invalid values must
|
|
@@ -6,7 +6,7 @@ function isJsonObject(value) {
|
|
|
6
6
|
}
|
|
7
7
|
/**
|
|
8
8
|
* The pre-rename key each machine feature was stored under, where one exists:
|
|
9
|
-
* `learning` was `decisions`
|
|
9
|
+
* `learning` was `decisions` and `knowledge` was `kb`. A manifest no
|
|
10
10
|
* command has rewritten since the rename can still hold only the legacy key.
|
|
11
11
|
* `memory` was never renamed.
|
|
12
12
|
*/
|
|
@@ -19,7 +19,7 @@ const LEGACY_KEYS = {
|
|
|
19
19
|
*
|
|
20
20
|
* Only an explicit boolean `false` switches a feature off. A missing key, a
|
|
21
21
|
* non-boolean value, or anything that is not a manifest-shaped object reads as
|
|
22
|
-
* ON — fail-open
|
|
22
|
+
* ON — fail-open, and the exact rule `queue_read_gates` applies in the
|
|
23
23
|
* shell hooks, so the CLI's status and the runtime never disagree about the
|
|
24
24
|
* same file.
|
|
25
25
|
*
|
|
@@ -37,7 +37,7 @@ const LEGACY_KEYS = {
|
|
|
37
37
|
* precedence exactly, so `devflow knowledge --status` reports a `kb: false`
|
|
38
38
|
* as disabled. queue_read_gates never reads knowledge, so there is no shell
|
|
39
39
|
* mirror. The knowledge write-back prose gate deliberately does not learn the
|
|
40
|
-
* legacy key (
|
|
40
|
+
* legacy key (no prompt text for a state only an un-upgraded install
|
|
41
41
|
* can hold); readManifest rewrites `kb` to `knowledge` on the next CLI run
|
|
42
42
|
* that loads the manifest, after which that gate reads the healed key.
|
|
43
43
|
*
|
package/dist/core/flags.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* D14: Typed registry — flags carry kind (boolean|enum|number|string), target
|
|
8
8
|
* (env|setting), and per-kind defaultValue. Neutral values delete their target
|
|
9
9
|
* key; active values write the appropriate payload. Number 0 is ACTIVE. Sink
|
|
10
|
-
* validation via coerceFlagValue (
|
|
10
|
+
* validation via coerceFlagValue (validate at the convergence
|
|
11
11
|
* point every caller reaches). applyFlags(settingsJson, FlagsRecord) is the
|
|
12
12
|
* sole API; init.ts works directly with FlagsRecord (no legacy string[] bridge).
|
|
13
13
|
*/
|
|
@@ -139,7 +139,7 @@ export const FLAG_REGISTRY = [
|
|
|
139
139
|
{
|
|
140
140
|
// Devflow fan-outs routinely exceed the upstream default of 20.
|
|
141
141
|
// Set to 40 by default so parallel Code/Review/Research waves don't
|
|
142
|
-
// silently queue. upstreamDefault recorded for display.
|
|
142
|
+
// silently queue. upstreamDefault recorded for display.
|
|
143
143
|
id: 'max-concurrent-subagents',
|
|
144
144
|
label: 'Max concurrent subagents',
|
|
145
145
|
description: 'Maximum number of subagents Claude Code will spawn concurrently',
|
|
@@ -150,7 +150,7 @@ export const FLAG_REGISTRY = [
|
|
|
150
150
|
recommended: true,
|
|
151
151
|
defaultValue: 40,
|
|
152
152
|
min: 1,
|
|
153
|
-
max: 100, // devflow sanity bound
|
|
153
|
+
max: 100, // devflow sanity bound
|
|
154
154
|
integer: true,
|
|
155
155
|
upstreamDefault: 20,
|
|
156
156
|
},
|
|
@@ -341,7 +341,7 @@ export const FLAG_REGISTRY = [
|
|
|
341
341
|
// ── Valued flags (number/enum/string) ────────────────────────────────────
|
|
342
342
|
{
|
|
343
343
|
// Domain: unset by default; set only when users want a non-default spawn depth.
|
|
344
|
-
// upstreamDefault: 3 (recorded for display).
|
|
344
|
+
// upstreamDefault: 3 (recorded for display). Sanity bound: max 10.
|
|
345
345
|
id: 'subagent-spawn-depth',
|
|
346
346
|
label: 'Max subagent spawn depth',
|
|
347
347
|
description: 'Maximum depth of nested subagent spawning',
|
|
@@ -352,7 +352,7 @@ export const FLAG_REGISTRY = [
|
|
|
352
352
|
recommended: false,
|
|
353
353
|
defaultValue: undefined,
|
|
354
354
|
min: 1,
|
|
355
|
-
max: 10, // devflow sanity bound
|
|
355
|
+
max: 10, // devflow sanity bound
|
|
356
356
|
integer: true,
|
|
357
357
|
upstreamDefault: 3,
|
|
358
358
|
},
|
|
@@ -384,7 +384,7 @@ export const FLAG_REGISTRY = [
|
|
|
384
384
|
},
|
|
385
385
|
{
|
|
386
386
|
// Upstream default: 30 min. 0 = disabled (still ACTIVE — written to env).
|
|
387
|
-
//
|
|
387
|
+
// Sanity bounds: max 1440 (24h). min 0 (0 = off, explicit value not neutral).
|
|
388
388
|
id: 'goal-checkin-minutes',
|
|
389
389
|
label: 'Goal check-in interval',
|
|
390
390
|
description: 'Interval in minutes for Claude to check in on task goals',
|
|
@@ -395,7 +395,7 @@ export const FLAG_REGISTRY = [
|
|
|
395
395
|
recommended: false,
|
|
396
396
|
defaultValue: undefined,
|
|
397
397
|
min: 0, // 0 = off (ACTIVE, not neutral — written as "0")
|
|
398
|
-
max: 1440, // devflow sanity bound: 24 hours
|
|
398
|
+
max: 1440, // devflow sanity bound: 24 hours
|
|
399
399
|
integer: true,
|
|
400
400
|
upstreamDefault: 30,
|
|
401
401
|
},
|
|
@@ -411,7 +411,7 @@ export const FLAG_REGISTRY = [
|
|
|
411
411
|
recommended: false,
|
|
412
412
|
defaultValue: undefined,
|
|
413
413
|
wrapKey: 'command',
|
|
414
|
-
maxLength: 256, // devflow sanity bound
|
|
414
|
+
maxLength: 256, // devflow sanity bound
|
|
415
415
|
},
|
|
416
416
|
{
|
|
417
417
|
// view-mode folded into the registry; neutralValue 'default' deletes the viewMode key.
|
|
@@ -474,7 +474,7 @@ export function isNeutral(flag, value) {
|
|
|
474
474
|
/**
|
|
475
475
|
* Map a record value to a TUI value.
|
|
476
476
|
*
|
|
477
|
-
* viewMode GLUE RULE (
|
|
477
|
+
* viewMode GLUE RULE (one shared definition): the mapping lives here,
|
|
478
478
|
* next to neutralValueOf — the definition it depends on — not across a module boundary.
|
|
479
479
|
* enum with neutralValue: neutralValue → null in TUI (null is the TUI representation
|
|
480
480
|
* of "use the default"; the key is deleted when persisted).
|
|
@@ -494,7 +494,7 @@ export function recordToTui(flag, v) {
|
|
|
494
494
|
/**
|
|
495
495
|
* Map a TUI value back to a record value.
|
|
496
496
|
*
|
|
497
|
-
* viewMode GLUE RULE (
|
|
497
|
+
* viewMode GLUE RULE (one shared definition): inverse of recordToTui,
|
|
498
498
|
* co-located with that function so the round-trip contract is auditable in one place.
|
|
499
499
|
* enum with neutralValue: null → neutralValue (e.g. 'default').
|
|
500
500
|
* All other values pass through unchanged.
|
|
@@ -509,7 +509,7 @@ export function tuiToRecord(flag, v) {
|
|
|
509
509
|
}
|
|
510
510
|
/**
|
|
511
511
|
* Validate and coerce `raw` to a safe value for `flag` at the sink.
|
|
512
|
-
* Returns null when the value is invalid (hostile-value defence
|
|
512
|
+
* Returns null when the value is invalid (hostile-value defence).
|
|
513
513
|
*
|
|
514
514
|
* Number invariants: finite, within [min, max], integer when required.
|
|
515
515
|
* String invariants: within maxLength, no control characters.
|
|
@@ -567,7 +567,7 @@ export function coerceFlagValue(flag, raw) {
|
|
|
567
567
|
* Parse a CLI text input to a FlagsRecordValue.
|
|
568
568
|
* 'unset' (literal) → null for any flag.
|
|
569
569
|
*
|
|
570
|
-
* Number branch uses strict decimal grammar (
|
|
570
|
+
* Number branch uses strict decimal grammar (invariant at the sink
|
|
571
571
|
* every caller reaches, not per-caller): rejects empty, padded, hex, exponent,
|
|
572
572
|
* and leading-zero forms. Equivalent to the TUI's strict parsing so both entry
|
|
573
573
|
* points share one grammar.
|
|
@@ -749,7 +749,7 @@ export function readViewMode(record) {
|
|
|
749
749
|
* Sanitize a FlagsRecord by coercing each known flag's value through
|
|
750
750
|
* coerceFlagValue.
|
|
751
751
|
*
|
|
752
|
-
* Known flag IDs (
|
|
752
|
+
* Known flag IDs (key-presence semantics):
|
|
753
753
|
* - explicit null input → kept as null (deliberately unset)
|
|
754
754
|
* - valid non-null input → kept as coerced value
|
|
755
755
|
* - invalid non-null input → KEY DROPPED (absent = adopt default on next init,
|
|
@@ -758,7 +758,7 @@ export function readViewMode(record) {
|
|
|
758
758
|
* Unknown flag IDs (forward-compat):
|
|
759
759
|
* - primitive values (boolean, number, string, null) → kept as-is
|
|
760
760
|
* - non-primitive values (objects, arrays) → DROPPED to avoid laundering
|
|
761
|
-
* untrusted shapes into FlagsRecordValue
|
|
761
|
+
* untrusted shapes into FlagsRecordValue
|
|
762
762
|
*
|
|
763
763
|
* D39: `__proto__`, `constructor`, `prototype` are always skipped.
|
|
764
764
|
*/
|
|
@@ -823,7 +823,7 @@ export function getDefaultFlagsRecord() {
|
|
|
823
823
|
* Migrate a legacy (string-array) enabled-flags manifest to a typed FlagsRecord.
|
|
824
824
|
* Called by manifest.ts self-healing when it encounters an old string-array manifest.
|
|
825
825
|
*
|
|
826
|
-
* Contract
|
|
826
|
+
* Contract:
|
|
827
827
|
* - knownIds defined → knownSet = knownIds ∪ enabledIds
|
|
828
828
|
* - knownIds undefined → knownSet = full current registry ∪ enabledIds
|
|
829
829
|
* (pre-knownFlags manifests: all flags known, so adopt-nothing is expressed
|
|
@@ -884,7 +884,7 @@ export function settingValueHoldsManagedShape(flag, value) {
|
|
|
884
884
|
return isDeepStrictEqual(value, guard);
|
|
885
885
|
}
|
|
886
886
|
/**
|
|
887
|
-
* D-ATTR-GUARD single-source predicate (consistency-01
|
|
887
|
+
* D-ATTR-GUARD single-source predicate (consistency-01).
|
|
888
888
|
*
|
|
889
889
|
* Returns true when the settings.json string contains a value at the flag's
|
|
890
890
|
* target key that equals the flag's managed shape (`settingDeleteGuard`).
|
|
@@ -926,7 +926,7 @@ export function settingHoldsManagedShape(settingsJson, flagId) {
|
|
|
926
926
|
*
|
|
927
927
|
* Delegates to `settingValueHoldsManagedShape` — the single equality oracle for
|
|
928
928
|
* managed-shape comparisons — so there is exactly one `isDeepStrictEqual` call
|
|
929
|
-
* across the entire apply/strip pipeline
|
|
929
|
+
* across the entire apply/strip pipeline.
|
|
930
930
|
*
|
|
931
931
|
* Single-source invariant: both the disable path (applyFlags neutral branch) and
|
|
932
932
|
* the uninstall path (stripFlags) collapse to this predicate.
|
|
@@ -980,7 +980,7 @@ function buildPayload(flag, value) {
|
|
|
980
980
|
* Apply a FlagsRecord to a settings JSON string.
|
|
981
981
|
*
|
|
982
982
|
* - Unknown flag IDs are skipped (forward-compatible with future flags).
|
|
983
|
-
* - `coerceFlagValue` is called at the sink before applying
|
|
983
|
+
* - `coerceFlagValue` is called at the sink before applying.
|
|
984
984
|
* - Neutral values delete their target key.
|
|
985
985
|
* - Env payloads for number flags are stringified ('40', never 40).
|
|
986
986
|
* - Setting payloads for string flags with wrapKey are shaped ({ command: v }).
|
|
@@ -988,7 +988,7 @@ function buildPayload(flag, value) {
|
|
|
988
988
|
* - `__proto__`, `constructor`, `prototype` keys are silently skipped.
|
|
989
989
|
*/
|
|
990
990
|
export function applyFlags(settingsJson, flags) {
|
|
991
|
-
// REL-M2 sink guard
|
|
991
|
+
// REL-M2 sink guard: a non-plain-object root (null, array, scalar)
|
|
992
992
|
// would cause a silent no-op or a confusing TypeError deep inside the loop.
|
|
993
993
|
// Throw early with a clear message so every caller path is self-guarding.
|
|
994
994
|
const root = JSON.parse(settingsJson);
|
|
@@ -1003,7 +1003,7 @@ export function applyFlags(settingsJson, flags) {
|
|
|
1003
1003
|
const flag = FLAG_REGISTRY_MAP.get(id);
|
|
1004
1004
|
if (!flag)
|
|
1005
1005
|
continue; // unknown id — skip for forward compat
|
|
1006
|
-
// Coerce at the sink (
|
|
1006
|
+
// Coerce at the sink (validate at the convergence point)
|
|
1007
1007
|
const safe = coerceFlagValue(flag, value);
|
|
1008
1008
|
if (isNeutral(flag, safe)) {
|
|
1009
1009
|
// Neutral → delete the target key
|
|
@@ -1045,7 +1045,7 @@ export function applyFlags(settingsJson, flags) {
|
|
|
1045
1045
|
* Cleans up empty env object. Strip-then-apply idempotence preserved (INV-1).
|
|
1046
1046
|
*/
|
|
1047
1047
|
export function stripFlags(settingsJson) {
|
|
1048
|
-
// REL-M2 sink guard
|
|
1048
|
+
// REL-M2 sink guard: mirror of applyFlags — throw early on a
|
|
1049
1049
|
// non-plain-object root so every caller path is self-guarding.
|
|
1050
1050
|
const root = JSON.parse(settingsJson);
|
|
1051
1051
|
if (root === null || typeof root !== 'object' || Array.isArray(root)) {
|
|
@@ -1122,7 +1122,7 @@ export function resolveFinalViewMode(current, selected, explicit) {
|
|
|
1122
1122
|
// ─── Fold-before-strip pipeline ───────────────────────────────────────────────
|
|
1123
1123
|
/**
|
|
1124
1124
|
* Fold-before-strip pipeline — the single authoritative entry point for all
|
|
1125
|
-
* settings.json mutation paths
|
|
1125
|
+
* settings.json mutation paths.
|
|
1126
1126
|
*
|
|
1127
1127
|
* Both `init.ts` and `persistFlagConfig` (flags.ts) MUST call this instead of
|
|
1128
1128
|
* invoking `stripFlags` + `applyFlags` directly; the invariant lives in the
|
|
@@ -1143,7 +1143,7 @@ export function resolveFinalViewMode(current, selected, explicit) {
|
|
|
1143
1143
|
* A flag is "claimed" when it is present and non-null in the claimed set.
|
|
1144
1144
|
* Claimed: record value wins (devflow previously set this value).
|
|
1145
1145
|
* Unclaimed: fold from settings — if the user has a value in settings.json,
|
|
1146
|
-
* adopt it into the record (
|
|
1146
|
+
* adopt it into the record (devflow takes ownership).
|
|
1147
1147
|
*
|
|
1148
1148
|
* Boolean flags: never folded — on/off is always record-driven.
|
|
1149
1149
|
*
|
|
@@ -1166,7 +1166,7 @@ export function resolveFinalViewMode(current, selected, explicit) {
|
|
|
1166
1166
|
*/
|
|
1167
1167
|
export function convergeFlagsIntoSettings(settingsJson, record, opts) {
|
|
1168
1168
|
// ── Step 1: fold view-mode (must read pre-strip) ──────────────────────────
|
|
1169
|
-
//
|
|
1169
|
+
// resolveExistingViewMode reads the viewMode key. stripFlags removes
|
|
1170
1170
|
// it as part of the view-mode registry entry. Reading after strip silently
|
|
1171
1171
|
// reverts an externally-set /focus.
|
|
1172
1172
|
const folded = {
|
|
@@ -1229,7 +1229,7 @@ export function convergeFlagsIntoSettings(settingsJson, record, opts) {
|
|
|
1229
1229
|
}
|
|
1230
1230
|
}
|
|
1231
1231
|
// ── Step 2b: adopt guarded boolean settings before the strip ─────────────
|
|
1232
|
-
// D-ATTR-ADOPT
|
|
1232
|
+
// D-ATTR-ADOPT: a delete guard is evidence about the VALUE,
|
|
1233
1233
|
// never about the record — a pre-existing on-disk key whose value matches the
|
|
1234
1234
|
// managed shape means devflow wrote it, so adopt it into the record now, before
|
|
1235
1235
|
// stripFlags can unconditionally remove it on the next line.
|
package/dist/core/fs-atomic.js
CHANGED
|
@@ -5,11 +5,10 @@ import { promises as fs } from 'fs';
|
|
|
5
5
|
* D34: Canonical atomic-write helper for the TypeScript CLI surface.
|
|
6
6
|
*
|
|
7
7
|
* Call sites: used by the CLI's exclusive-write call sites (migrations, init, post-install,
|
|
8
|
-
* uninstall, security, ambient, memory, HUD
|
|
9
|
-
* The CJS counterpart (`writeExclusive` in `src/assets/scripts/hooks/
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* retry logic here MUST be mirrored in both CJS files.
|
|
8
|
+
* uninstall, security, ambient, memory, HUD).
|
|
9
|
+
* The CJS counterpart (`writeExclusive` in `src/assets/scripts/hooks/lib/learning-store.cjs`)
|
|
10
|
+
* intentionally remains a separate implementation — same semantics, different module
|
|
11
|
+
* system. Any change to the retry logic here MUST be mirrored there.
|
|
13
12
|
*/
|
|
14
13
|
/**
|
|
15
14
|
* Atomically write `filePath` by writing to a sibling `.tmp` then renaming.
|
|
@@ -34,7 +33,7 @@ export async function writeFileAtomicExclusive(filePath, data) {
|
|
|
34
33
|
// PID-scope the tmp name so concurrent writers from different processes
|
|
35
34
|
// (e.g., two Claude Code sessions) never collide on the same .tmp path.
|
|
36
35
|
// mirrors proxy-log.ts rotation at src/core/proxy-log.ts which PID-scopes
|
|
37
|
-
// for the same reason.
|
|
36
|
+
// for the same reason.
|
|
38
37
|
const tmp = `${filePath}.tmp.${process.pid}`;
|
|
39
38
|
try {
|
|
40
39
|
await fs.writeFile(tmp, data, { encoding: 'utf-8', flag: 'wx' });
|
|
@@ -58,7 +57,7 @@ export async function writeFileAtomicExclusive(filePath, data) {
|
|
|
58
57
|
//
|
|
59
58
|
// Non-fatal path: if stat fails (ENOENT → fresh file, or any other I/O
|
|
60
59
|
// error), skip chmod and keep the umask default — the write must still
|
|
61
|
-
// complete correctly
|
|
60
|
+
// complete correctly.
|
|
62
61
|
try {
|
|
63
62
|
const { mode } = await fs.stat(filePath);
|
|
64
63
|
// mode includes file-type bits; mask to permission bits only for chmod.
|
|
@@ -1,92 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @file learning-queue-cleanup.ts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `src/cli/commands/learning.ts
|
|
6
|
-
*
|
|
7
|
-
* - `drainLearningQueue` — used by `devflow learning --clear` / `--disable`
|
|
4
|
+
* `drainLearningQueue`, the learning queue drain shared by `devflow learning
|
|
5
|
+
* --clear` and `--disable` (src/cli/commands/learning.ts) and by the drain
|
|
6
|
+
* `devflow init` runs when learning is switched off (src/cli/commands/init.ts).
|
|
8
7
|
*/
|
|
9
8
|
import { promises as fs } from 'fs';
|
|
10
|
-
import
|
|
11
|
-
import { getLearningPendingTurnsPath, getLearningPendingTurnsProcessingPath, } from './project-paths.js';
|
|
12
|
-
// ---------------------------------------------------------------------------
|
|
13
|
-
// Legacy marker-pipeline sweep
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
/** Fixed-name stamps left by the retired dream marker pipeline. */
|
|
16
|
-
const LEGACY_FIXED_STAMPS = ['.decisions-runs-today', '.curation-last', '.processor-spawned-at'];
|
|
17
|
-
/** Per-session marker variants: (decisions|curation).*.{json,processing,retries,failed} */
|
|
18
|
-
function isLegacyPerSessionMarker(name) {
|
|
19
|
-
return ((name.startsWith('decisions.') || name.startsWith('curation.')) &&
|
|
20
|
-
(name.endsWith('.json') || name.endsWith('.processing') || name.endsWith('.retries') || name.endsWith('.failed')));
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Sweep legacy marker-pipeline files from a `.devflow/learning/` directory:
|
|
24
|
-
* the fixed-name stamps above, plus per-session `decisions.*`/`curation.*`
|
|
25
|
-
* markers. Never touches `learning.json` or the live
|
|
26
|
-
* `.pending-turns.jsonl`/`.pending-turns.processing` queue files.
|
|
27
|
-
*
|
|
28
|
-
* ENOENT-idempotent (missing learning dir or already-removed files are not
|
|
29
|
-
* errors). Non-ENOENT errors are rethrown — callers that need best-effort
|
|
30
|
-
* semantics (e.g. `--reset`, which must still finish releasing its lock)
|
|
31
|
-
* should wrap the call in their own try/catch.
|
|
32
|
-
*
|
|
33
|
-
* @returns number of files removed
|
|
34
|
-
*/
|
|
35
|
-
export async function sweepLegacyDreamMarkers(learningDir) {
|
|
36
|
-
let removed = 0;
|
|
37
|
-
for (const name of LEGACY_FIXED_STAMPS) {
|
|
38
|
-
try {
|
|
39
|
-
await fs.unlink(path.join(learningDir, name));
|
|
40
|
-
removed++;
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
const code = err.code;
|
|
44
|
-
if (code !== 'ENOENT')
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
try {
|
|
49
|
-
const entries = await fs.readdir(learningDir);
|
|
50
|
-
for (const entry of entries) {
|
|
51
|
-
if (isLegacyPerSessionMarker(entry)) {
|
|
52
|
-
try {
|
|
53
|
-
await fs.unlink(path.join(learningDir, entry));
|
|
54
|
-
removed++;
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
const code = err.code;
|
|
58
|
-
if (code !== 'ENOENT')
|
|
59
|
-
throw err;
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
catch (err) {
|
|
65
|
-
const code = err.code;
|
|
66
|
-
if (code !== 'ENOENT')
|
|
67
|
-
throw err;
|
|
68
|
-
}
|
|
69
|
-
return removed;
|
|
70
|
-
}
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// Live queue drain
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
9
|
+
import { getLearningClaimOwnerPath, getLearningPendingTurnsPath, getLearningPendingTurnsProcessingPath, } from './project-paths.js';
|
|
74
10
|
/**
|
|
75
|
-
* Drain the learning (decisions-detection) pending-turns queue
|
|
76
|
-
* don't process later — used by
|
|
77
|
-
*
|
|
78
|
-
* the desired outcome in both cases.
|
|
11
|
+
* Drain the learning (decisions-detection) pending-turns queue, its claimed
|
|
12
|
+
* batch and the claim's owner file so stale turns don't process later — used by
|
|
13
|
+
* both `--clear` and `--disable`. A mid-run Learning agent whose claimed batch
|
|
14
|
+
* vanishes aborts without changes, which is the desired outcome in both cases.
|
|
15
|
+
* ENOENT-tolerant; other errors propagate.
|
|
79
16
|
*/
|
|
80
17
|
export async function drainLearningQueue(gitRoot) {
|
|
18
|
+
const unlinkIfPresent = (file) => fs.unlink(file).catch((e) => {
|
|
19
|
+
if (e.code !== 'ENOENT')
|
|
20
|
+
throw e;
|
|
21
|
+
});
|
|
81
22
|
await Promise.all([
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}),
|
|
86
|
-
fs.unlink(getLearningPendingTurnsProcessingPath(gitRoot)).catch((e) => {
|
|
87
|
-
if (e.code !== 'ENOENT')
|
|
88
|
-
throw e;
|
|
89
|
-
}),
|
|
23
|
+
unlinkIfPresent(getLearningPendingTurnsPath(gitRoot)),
|
|
24
|
+
unlinkIfPresent(getLearningPendingTurnsProcessingPath(gitRoot)),
|
|
25
|
+
unlinkIfPresent(getLearningClaimOwnerPath(gitRoot)),
|
|
90
26
|
]);
|
|
91
27
|
}
|
|
92
28
|
//# sourceMappingURL=learning-queue-cleanup.js.map
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI's view of the learning store — a typed seam onto the package's own
|
|
3
|
+
* `hooks/lib/learning-store.cjs`, never a second implementation of it.
|
|
4
|
+
*
|
|
5
|
+
* D-LEARNING-STORE-SEAM: `devflow learning` reads and writes the learning tree only
|
|
6
|
+
* through the store module json-helper's ops and the hooks run, loaded with the
|
|
7
|
+
* evidence-policy seam's `loadScript` from the package's own scripts directory and
|
|
8
|
+
* shape-checked against LEARNING_STORE_SURFACE; the interfaces below are
|
|
9
|
+
* TRANSCRIBED from the store's JSDoc and are this side's only shape authority.
|
|
10
|
+
* Reason: the CLI's own TypeScript reader judged every row by the v1 shape, so it
|
|
11
|
+
* counted v2 observations as invalid, and its `--clear` truncated the log without
|
|
12
|
+
* the lock every learning writer takes.
|
|
13
|
+
*
|
|
14
|
+
* As with the evidence resolver (D-POLICY-CJS-SEAM), the CLI loads the package
|
|
15
|
+
* copy, which `npm` installs with this CLI, not `~/.devflow/scripts`, which
|
|
16
|
+
* `devflow init` refreshes: the two can differ until the next init.
|
|
17
|
+
*/
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
import { scriptsDir } from './assets.js';
|
|
20
|
+
import { loadScript } from './evidence-policy.js';
|
|
21
|
+
/** The store, relative to src/assets/scripts/ (and ~/.devflow/scripts/). */
|
|
22
|
+
export const LEARNING_STORE_SCRIPT_NAME = join('hooks', 'lib', 'learning-store.cjs');
|
|
23
|
+
/**
|
|
24
|
+
* Every key of LearningStoreModule and the runtime kind the loader requires of
|
|
25
|
+
* it. `satisfies` makes the compiler reject an interface key missing here.
|
|
26
|
+
*/
|
|
27
|
+
export const LEARNING_STORE_SURFACE = Object.freeze({
|
|
28
|
+
INACTIVE_STATUSES: 'string-array',
|
|
29
|
+
ANCHOR_ID_RE: 'regexp',
|
|
30
|
+
readLearningState: 'function',
|
|
31
|
+
buildListing: 'function',
|
|
32
|
+
readListing: 'function',
|
|
33
|
+
formatListing: 'function',
|
|
34
|
+
showByKey: 'function',
|
|
35
|
+
restoreAnchor: 'function',
|
|
36
|
+
clearUnreferenced: 'function',
|
|
37
|
+
resetLearning: 'function',
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Load the learning store from `dir` (default: the package's own scripts
|
|
41
|
+
* directory) and shape-check its surface. Never throws: a missing file is
|
|
42
|
+
* `not-found`; a module that throws on load or lacks a surface key is `unusable`.
|
|
43
|
+
*/
|
|
44
|
+
export function loadLearningStore(dir = scriptsDir()) {
|
|
45
|
+
return loadScript(join(dir, LEARNING_STORE_SCRIPT_NAME), LEARNING_STORE_SURFACE);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Why the store cannot be used, and the remedy: a package reinstall, since the
|
|
49
|
+
* CLI loads the package's own copy, which `devflow init` does not restore.
|
|
50
|
+
*/
|
|
51
|
+
export function formatLearningStoreUnavailable(error) {
|
|
52
|
+
switch (error.kind) {
|
|
53
|
+
case 'not-found': return 'learning store not found — reinstall devflow-kit';
|
|
54
|
+
case 'unusable': return 'learning store failed to load — reinstall devflow-kit';
|
|
55
|
+
default: {
|
|
56
|
+
const exhaustive = error;
|
|
57
|
+
return exhaustive;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=learning-store.js.map
|
package/dist/core/manifest.js
CHANGED
|
@@ -54,7 +54,7 @@ function parseManifestFlags(features, knownFlags) {
|
|
|
54
54
|
/**
|
|
55
55
|
* Read and parse the manifest file. Returns null if missing or corrupt.
|
|
56
56
|
*
|
|
57
|
-
* Self-heals the following on-disk inconsistencies
|
|
57
|
+
* Self-heals the following on-disk inconsistencies:
|
|
58
58
|
* - features.kb → features.knowledge rename (both absent → true, D-FEATURES-ABSENT-ON)
|
|
59
59
|
* - features.decisions → features.learning rename (both absent → true, D-FEATURES-ABSENT-ON)
|
|
60
60
|
* - features.flags as string[] → FlagsRecord (via migrateLegacyFlagsToRecord)
|
|
@@ -103,7 +103,7 @@ export async function readManifest(devflowDir) {
|
|
|
103
103
|
const knowledge = typeof features.knowledge === 'boolean' ? features.knowledge
|
|
104
104
|
: typeof features.kb === 'boolean' ? features.kb
|
|
105
105
|
: true;
|
|
106
|
-
// Self-heal: rename features.decisions → features.learning on disk
|
|
106
|
+
// Self-heal: rename features.decisions → features.learning on disk.
|
|
107
107
|
// Coalesce: features.learning wins; fall back to features.decisions; default ON.
|
|
108
108
|
// D-LEARNING-LEGACY-DECISIONS: isMachineFeatureOn and queue_read_gates apply
|
|
109
109
|
// this exact precedence, so the legacy key is honoured before the heal lands.
|
|
@@ -122,7 +122,7 @@ export async function readManifest(devflowDir) {
|
|
|
122
122
|
// `flagsWereLegacy` is true only when the on-disk shape was a string[] (Case A),
|
|
123
123
|
// keeping the needsHeal predicate in lockstep with the parse branch above.
|
|
124
124
|
const { flags: parsedFlags, legacy: flagsWereLegacy } = parseManifestFlags(features, knownFlags);
|
|
125
|
-
//
|
|
125
|
+
// D39: sanitize all values; block prototype pollution keys.
|
|
126
126
|
const sanitizedFlags = sanitizeFlagsRecord(parsedFlags);
|
|
127
127
|
// needsHeal when any legacy artifact is present on disk
|
|
128
128
|
const needsHeal = features.kb !== undefined ||
|
|
@@ -154,14 +154,14 @@ export async function readManifest(devflowDir) {
|
|
|
154
154
|
security: typeof features.security === 'string' && SECURITY_MODES.includes(features.security)
|
|
155
155
|
? features.security
|
|
156
156
|
: undefined,
|
|
157
|
-
// Self-heal: absent proxy field defaults to false
|
|
157
|
+
// Self-heal: absent proxy field defaults to false
|
|
158
158
|
proxy: typeof features.proxy === 'boolean' ? features.proxy : false,
|
|
159
159
|
// Self-heal: absent/malformed compliance → {enabled:false, frameworks:[]}
|
|
160
160
|
compliance: normalizeComplianceFeature(features.compliance),
|
|
161
161
|
// Self-heal: absent/malformed/unknown tracker → {provider:'github'} (AC-3.21).
|
|
162
162
|
// Deliberately NOT in the hard-null set above — see the field's doc comment.
|
|
163
163
|
// Healing here is silent and emits no DEGRADED: that is the correct
|
|
164
|
-
//
|
|
164
|
+
// self-heal behaviour, and a different condition from a per-repo config
|
|
165
165
|
// value outside the domain (which does emit `unknown tracker provider`).
|
|
166
166
|
tracker: normalizeTrackerFeature(features.tracker),
|
|
167
167
|
},
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* Pure module — zero I/O. Every question a caller asks about a HOST is answered
|
|
5
5
|
* with a Result; callers own every filesystem call and every process exit.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* Pure core-layer module, no build-script or adapter concerns.
|
|
8
8
|
* The registries below are agent-neutral, so what is DERIVED from them is derived
|
|
9
9
|
* here rather than inside an install target — a target adapter computing a build
|
|
10
10
|
* fact, with tests importing that adapter to learn it, is the seam inverting.
|
|
11
|
-
*
|
|
11
|
+
* No process.exit(); every fallible path returns Result. The
|
|
12
12
|
* exiting shell is scripts/build-mds.ts, which renders these errors into its
|
|
13
13
|
* pre-existing messages.
|
|
14
14
|
*
|
|
@@ -85,7 +85,7 @@ export function validateOutputName(name) {
|
|
|
85
85
|
* `tracker/jira/`, and `tracker/mcp.md` would read as a third provider.
|
|
86
86
|
* Relaxing the shared rule instead would have admitted `_anything.md`
|
|
87
87
|
* as a command or an agent basename too — a widening across all three build
|
|
88
|
-
* destinations to buy a property only this one needs (
|
|
88
|
+
* destinations to buy a property only this one needs (classify the case,
|
|
89
89
|
* never blanket-widen).
|
|
90
90
|
*
|
|
91
91
|
* Every other guarantee is inherited by delegation, so the dot-segment,
|
|
@@ -127,8 +127,8 @@ export const AGENTS_OUTPUT_DIR = 'dist/agents';
|
|
|
127
127
|
* the installer decides which skill install triggers the reference overlay from
|
|
128
128
|
* it, and the init summary renders `prefixSkillName()` of it. Retyped at each of
|
|
129
129
|
* those three sites, moving the references to another skill would mean finding
|
|
130
|
-
* all three spellings with nothing failing if only two were found —
|
|
131
|
-
*
|
|
130
|
+
* all three spellings with nothing failing if only two were found — a hardcoded
|
|
131
|
+
* spelling that still resolves.
|
|
132
132
|
*
|
|
133
133
|
* Bare, not `devflow:`-prefixed: the build writes to `dist/skills/git/…` while
|
|
134
134
|
* the install target is `skills/devflow:git/`. prefixSkillName is what spans that
|
|
@@ -162,7 +162,7 @@ const ALLOWED_OUTPUT_DIRS = [
|
|
|
162
162
|
*
|
|
163
163
|
* Exported so guards assert the build's refusal text against the table itself
|
|
164
164
|
* rather than against a retyped literal: adding a destination then rewrites both
|
|
165
|
-
* the message and its assertion from one edit (
|
|
165
|
+
* the message and its assertion from one edit (the expectation must
|
|
166
166
|
* come from the thing under test, not a copy of it).
|
|
167
167
|
*/
|
|
168
168
|
export const ALLOWED_OUTPUT_DIR_NAMES = ALLOWED_OUTPUT_DIRS.map(entry => entry.dir);
|
|
@@ -223,7 +223,7 @@ export function resolveOutputDir(root, declared) {
|
|
|
223
223
|
* reverse alone lets a listed op silently emit nothing.
|
|
224
224
|
*
|
|
225
225
|
* The list is long from its first commit on purpose. A one- or two-element list
|
|
226
|
-
* makes every parity assertion over it vacuous (GAP-42
|
|
226
|
+
* makes every parity assertion over it vacuous (GAP-42) and is
|
|
227
227
|
* structurally identical to the single-arm conditional AC-1.2 forbids, so
|
|
228
228
|
* expandVariants refuses a pair list below MIN_VARIANT_PAIRS.
|
|
229
229
|
*/
|
|
@@ -283,8 +283,8 @@ export const TRACKER_GITHUB_OPS = TRACKER_OPS;
|
|
|
283
283
|
* carries one pointer to each.
|
|
284
284
|
*
|
|
285
285
|
* 9 entries, one above {@link MIN_VARIANT_PAIRS}, which is a floor and not a
|
|
286
|
-
* target: a shorter roster makes every parity assertion over it vacuous (GAP-42
|
|
287
|
-
*
|
|
286
|
+
* target: a shorter roster makes every parity assertion over it vacuous (GAP-42)
|
|
287
|
+
* and `expandVariants` refuses the build, so the roster can grow
|
|
288
288
|
* but never drop below 8. `update-pr-evidence` (#363) is the ninth — it edits the
|
|
289
289
|
* PR body and comments on the PR, both GitHub whatever the tracker is.
|
|
290
290
|
*/
|
|
@@ -558,7 +558,7 @@ export const MIN_VARIANT_PAIRS = 8;
|
|
|
558
558
|
* writes.
|
|
559
559
|
*
|
|
560
560
|
* Pure and total: every refusal is a Result, so the build shell keeps its single
|
|
561
|
-
* exit
|
|
561
|
+
* exit. The expansion is deliberately flat rather than nested —
|
|
562
562
|
* one list of destinations is what the plan pass needs to detect two hosts
|
|
563
563
|
* claiming one file, and a nested shape would have to be flattened there anyway.
|
|
564
564
|
*
|
|
@@ -642,14 +642,14 @@ export function expandVariants(modules = resolveVariantModules()) {
|
|
|
642
642
|
* Lives beside the registry it reads rather than in the Claude Code installer that
|
|
643
643
|
* consumes it: nothing about the answer is Claude-Code-specific, and the packaging
|
|
644
644
|
* and containment tests that read it are asking the BUILD what it emits, not
|
|
645
|
-
* asking an install target
|
|
645
|
+
* asking an install target.
|
|
646
646
|
*
|
|
647
647
|
* Asserts where its siblings return a Result. The registry is a compile-time
|
|
648
648
|
* constant, so a refusal is a programming error rather than an install-time
|
|
649
649
|
* degradation: no caller could sensibly continue, and every caller would otherwise
|
|
650
650
|
* carry the same impossible branch. The full refusal is rendered and not just its
|
|
651
651
|
* `kind` — the payload is what names the offending module and op, and a payload
|
|
652
|
-
* nothing reads is a payload nothing maintains
|
|
652
|
+
* nothing reads is a payload nothing maintains. Same rendering the
|
|
653
653
|
* build's own refusal sinks use (scripts/build-mds.ts).
|
|
654
654
|
*/
|
|
655
655
|
export function generatedReferenceManifest() {
|
|
@@ -721,7 +721,7 @@ export const VARIANT_SECTION_MARKER_RE = /^<!-- op: (_?[a-z0-9][a-z0-9._-]{0,63}
|
|
|
721
721
|
*
|
|
722
722
|
* Bidirectional, and both directions are load-bearing:
|
|
723
723
|
* - unknown-section — the body carries a section for an op the registry does
|
|
724
|
-
* not name, so a file would ship that nothing loads
|
|
724
|
+
* not name, so a file would ship that nothing loads;
|
|
725
725
|
* - missing-section — the registry names an op the body does not cover, so the
|
|
726
726
|
* preamble's load instruction resolves to nothing at runtime.
|
|
727
727
|
* A forward-only check passes on either half of that pair.
|