@momentiq/dark-factory-cli 2.18.0 → 3.0.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.
Files changed (117) hide show
  1. package/dist/adapters/cursor-sdk.d.ts.map +1 -1
  2. package/dist/adapters/cursor-sdk.js +14 -2
  3. package/dist/adapters/cursor-sdk.js.map +1 -1
  4. package/dist/adapters/index.d.ts +1 -0
  5. package/dist/adapters/index.d.ts.map +1 -1
  6. package/dist/adapters/index.js +6 -0
  7. package/dist/adapters/index.js.map +1 -1
  8. package/dist/adapters/mode-scope.d.ts +86 -0
  9. package/dist/adapters/mode-scope.d.ts.map +1 -0
  10. package/dist/adapters/mode-scope.js +456 -0
  11. package/dist/adapters/mode-scope.js.map +1 -0
  12. package/dist/cli.d.ts +5 -0
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +17 -1
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/mode.d.ts.map +1 -1
  17. package/dist/commands/mode.js +135 -10
  18. package/dist/commands/mode.js.map +1 -1
  19. package/dist/commands/objectives.d.ts +27 -3
  20. package/dist/commands/objectives.d.ts.map +1 -1
  21. package/dist/commands/objectives.js +347 -53
  22. package/dist/commands/objectives.js.map +1 -1
  23. package/dist/cycle-doc-validator/validate_cycle_doc.py +128 -11
  24. package/dist/doctor.d.ts.map +1 -1
  25. package/dist/doctor.js +8 -4
  26. package/dist/doctor.js.map +1 -1
  27. package/dist/evidence/index.d.ts +1 -0
  28. package/dist/evidence/index.d.ts.map +1 -1
  29. package/dist/evidence/index.js +5 -0
  30. package/dist/evidence/index.js.map +1 -1
  31. package/dist/evidence/ui-visual/embed.d.ts +65 -0
  32. package/dist/evidence/ui-visual/embed.d.ts.map +1 -0
  33. package/dist/evidence/ui-visual/embed.js +176 -0
  34. package/dist/evidence/ui-visual/embed.js.map +1 -0
  35. package/dist/evidence/ui-visual/index.d.ts +2 -0
  36. package/dist/evidence/ui-visual/index.d.ts.map +1 -0
  37. package/dist/evidence/ui-visual/index.js +6 -0
  38. package/dist/evidence/ui-visual/index.js.map +1 -0
  39. package/dist/index.d.ts +1 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +1 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/mcp/cycle-doc/parser.d.ts +3 -3
  44. package/dist/mcp/cycle-doc/parser.d.ts.map +1 -1
  45. package/dist/mcp/cycle-doc/parser.js +2 -2
  46. package/dist/mcp/cycle-doc/parser.js.map +1 -1
  47. package/dist/mcp/tools/doctor.d.ts +5 -0
  48. package/dist/mcp/tools/doctor.d.ts.map +1 -1
  49. package/dist/mcp/tools/doctor.js +10 -1
  50. package/dist/mcp/tools/doctor.js.map +1 -1
  51. package/dist/mcp/tools/review-bypass.d.ts +5 -0
  52. package/dist/mcp/tools/review-bypass.d.ts.map +1 -1
  53. package/dist/mcp/tools/review-bypass.js +9 -1
  54. package/dist/mcp/tools/review-bypass.js.map +1 -1
  55. package/dist/mode/apply.d.ts +8 -0
  56. package/dist/mode/apply.d.ts.map +1 -0
  57. package/dist/mode/apply.js +118 -0
  58. package/dist/mode/apply.js.map +1 -0
  59. package/dist/mode/context.d.ts +50 -0
  60. package/dist/mode/context.d.ts.map +1 -0
  61. package/dist/mode/context.js +127 -0
  62. package/dist/mode/context.js.map +1 -0
  63. package/dist/mode/critic-registration.d.ts +66 -0
  64. package/dist/mode/critic-registration.d.ts.map +1 -0
  65. package/dist/mode/critic-registration.js +279 -0
  66. package/dist/mode/critic-registration.js.map +1 -0
  67. package/dist/mode/guard.d.ts +15 -0
  68. package/dist/mode/guard.d.ts.map +1 -0
  69. package/dist/mode/guard.js +160 -0
  70. package/dist/mode/guard.js.map +1 -0
  71. package/dist/mode/init.d.ts.map +1 -1
  72. package/dist/mode/init.js +14 -5
  73. package/dist/mode/init.js.map +1 -1
  74. package/dist/mode/loadConfig.d.ts +34 -0
  75. package/dist/mode/loadConfig.d.ts.map +1 -0
  76. package/dist/mode/loadConfig.js +38 -0
  77. package/dist/mode/loadConfig.js.map +1 -0
  78. package/dist/mode/prompts/designer-baseline.md +47 -0
  79. package/dist/mode/resolve.d.ts +1 -1
  80. package/dist/mode/resolve.d.ts.map +1 -1
  81. package/dist/mode/resolve.js +19 -3
  82. package/dist/mode/resolve.js.map +1 -1
  83. package/dist/mode/scope-policy.d.ts +41 -0
  84. package/dist/mode/scope-policy.d.ts.map +1 -0
  85. package/dist/mode/scope-policy.js +97 -0
  86. package/dist/mode/scope-policy.js.map +1 -0
  87. package/dist/mode/status.d.ts.map +1 -1
  88. package/dist/mode/status.js +2 -9
  89. package/dist/mode/status.js.map +1 -1
  90. package/dist/onboard/validate.d.ts +24 -6
  91. package/dist/onboard/validate.d.ts.map +1 -1
  92. package/dist/onboard/validate.js +87 -14
  93. package/dist/onboard/validate.js.map +1 -1
  94. package/dist/skills/config.d.ts +2 -113
  95. package/dist/skills/config.d.ts.map +1 -1
  96. package/dist/skills/config.js +19 -62
  97. package/dist/skills/config.js.map +1 -1
  98. package/dist/skills/index.d.ts +1 -1
  99. package/dist/skills/index.d.ts.map +1 -1
  100. package/dist/skills/index.js +1 -1
  101. package/dist/skills/index.js.map +1 -1
  102. package/dist/skills/install.d.ts.map +1 -1
  103. package/dist/skills/install.js +7 -0
  104. package/dist/skills/install.js.map +1 -1
  105. package/package.json +2 -2
  106. package/skills/designer-brief/SKILL.md.tmpl +74 -0
  107. package/skills/designer-brief/skill.json +17 -0
  108. package/skills/designer-build/SKILL.md.tmpl +63 -0
  109. package/skills/designer-build/skill.json +17 -0
  110. package/skills/designer-handoff/SKILL.md.tmpl +64 -0
  111. package/skills/designer-handoff/skill.json +17 -0
  112. package/skills/designer-ship/SKILL.md.tmpl +77 -0
  113. package/skills/designer-ship/skill.json +17 -0
  114. package/skills/objectives/SKILL.md.tmpl +16 -1
  115. package/skills/verify/producer/ui-visual-surfaces.ts +261 -0
  116. package/skills/verify/producer/ui-visual.config.ts +22 -0
  117. package/skills/verify/producer/ui-visual.producer.spec.ts +137 -0
@@ -0,0 +1,127 @@
1
+ // Cycle 27 Phase B — `df mode context --hook`: build the SessionStart
2
+ // additionalContext for an operator-mode session. Persona (baseline tier,
3
+ // shipped as a dist asset with the DF-PROFILE sentinel) + live mode summary
4
+ // + the repo's DESIGN.md as design doctrine.
5
+ import { readFileSync, realpathSync, statSync } from "node:fs";
6
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ const HERE = dirname(fileURLToPath(import.meta.url));
9
+ /**
10
+ * Load the baseline persona asset. Lives alongside this module in both src
11
+ * (tests) and dist (runtime — the build's copy-assets step ships it), so an
12
+ * import.meta.url-relative read works in both.
13
+ */
14
+ export function loadBaselinePersona() {
15
+ return readFileSync(resolve(HERE, "prompts", "designer-baseline.md"), "utf8");
16
+ }
17
+ const STRICT_REL_PATH = /^[A-Za-z0-9._\/-]+$/;
18
+ /**
19
+ * The strict repo-relative path predicate used for the DISPLAY path only —
20
+ * pure lexical, no filesystem access. `buildModeContext` echoes the configured
21
+ * `designMd` into the LLM-consumed SessionStart note, so it gates on this and
22
+ * shows a neutral placeholder for anything that is not a genuine strict
23
+ * repo-relative path. True only for a non-empty, length-bounded value built
24
+ * from safe path characters (letters, digits, `.` `_` `-` `/`) that is NOT
25
+ * absolute and contains NO empty/current/parent (`.`/`..`) segments. The
26
+ * character grammar keeps the value free of spaces, newlines, backticks, and
27
+ * markdown metacharacters, so a hostile catalog value cannot express multi-word
28
+ * instructions or open a new markdown block in the injected context.
29
+ *
30
+ * This is deliberately STRICTER than what `safeDesignMdPath` accepts for reads:
31
+ * read-safety asks "is this a real file inside the repo?" (spaces are fine);
32
+ * display-safety asks "is this safe to echo into model context?" (they are not).
33
+ * Keeping them separate avoids narrowing the public `designSystem.designMd`
34
+ * string contract while still hardening the injected note.
35
+ */
36
+ export function isStrictRepoRelativePath(p) {
37
+ if (p.length === 0 || p.length > 200)
38
+ return false;
39
+ if (!STRICT_REL_PATH.test(p))
40
+ return false;
41
+ if (isAbsolute(p))
42
+ return false;
43
+ for (const segment of p.split("/")) {
44
+ if (segment === "" || segment === "." || segment === "..")
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ /**
50
+ * Resolve the catalog's `designSystem.designMd` value safely: it is a
51
+ * REPO-RELATIVE path by contract, and this hook injects the file's contents
52
+ * into session context — so the check must hold against the REAL filesystem,
53
+ * not just lexically. Returns the resolved path only when:
54
+ * - the value is relative and its lexical resolution stays under repoRoot,
55
+ * - the file exists and is a regular file, and
56
+ * - its realpath (symlinks followed) still lives under the repo's realpath
57
+ * (a repo-local DESIGN.md symlinked to ~/.ssh/id_rsa returns null).
58
+ * A missing file also returns null — "unseeded" is a normal state the
59
+ * caller narrates; it must never be distinguishable from "blocked" in ways
60
+ * that leak what exists outside the repo.
61
+ *
62
+ * This accepts ANY real repo-relative file (paths with spaces included): it
63
+ * enforces filesystem safety, NOT the stricter display grammar, so it does not
64
+ * narrow the public `designSystem.designMd` string contract.
65
+ */
66
+ export function safeDesignMdPath(repoRoot, designMd) {
67
+ if (isAbsolute(designMd))
68
+ return null;
69
+ const resolved = resolve(repoRoot, designMd);
70
+ const rel = relative(repoRoot, resolved);
71
+ // Reject only TRUE parent-traversal: the bare `..`, or a path whose first
72
+ // segment is `..` (`../x`). `rel.startsWith("..")` alone would also reject a
73
+ // legitimate repo-local path whose first segment merely BEGINS with `..`
74
+ // (e.g. `..docs/DESIGN.md`), so key on the separator — matching the realpath
75
+ // check below.
76
+ if (rel === "" || rel === ".." || rel.startsWith(`..${sep}`))
77
+ return null;
78
+ try {
79
+ if (!statSync(resolved).isFile())
80
+ return null;
81
+ const realTarget = realpathSync(resolved);
82
+ const realRoot = realpathSync(repoRoot);
83
+ const realRel = relative(realRoot, realTarget);
84
+ if (realRel === "" || realRel === ".." || realRel.startsWith(`..${sep}`))
85
+ return null;
86
+ return resolved;
87
+ }
88
+ catch {
89
+ return null; // missing, unreadable, or unresolvable — treat as unseeded
90
+ }
91
+ }
92
+ export function buildModeContext(resolved, designMdContent) {
93
+ const { modeName, mode } = resolved;
94
+ const persona = loadBaselinePersona();
95
+ const summary = [
96
+ `## Active operator mode: ${modeName}`,
97
+ "",
98
+ `- Editable areas (writeScopes): ${(mode.writeScopes ?? []).join(", ") || "(none configured)"}`,
99
+ `- Protected areas (always off-limits, they win on overlap): ${(mode.protectedPaths ?? []).join(", ") || "(none)"}`,
100
+ `- Dependency policy: ${mode.dependencyPolicy ?? "(unset)"} — dependency changes ${mode.dependencyPolicy === "none" ? "always escalate via handoff" : "may escalate"}`,
101
+ mode.devServer?.command
102
+ ? `- Dev server for visual verification: \`${mode.devServer.command}\`${mode.devServer.url ? ` → ${mode.devServer.url}` : ""}`
103
+ : `- Dev server: not configured — ask an engineer to set modes.${modeName}.devServer before visual work`,
104
+ `- Visual evidence: ${mode.evidence?.visual ?? "(unset)"}`,
105
+ ].join("\n");
106
+ // designSystem.designMd is a repo-relative path that may be nested (not
107
+ // necessarily the repo root), so the "unseeded" note names the configured
108
+ // location rather than hardcoding "the repo root". This value is echoed into
109
+ // the LLM-consumed SessionStart context, where markdown fencing is NOT a
110
+ // prompt-injection boundary — so echo it only when it is a strict
111
+ // repo-relative path (isStrictRepoRelativePath); anything else (absolute,
112
+ // `.`/`..` traversal, or containing spaces/newlines/markdown metachars) is
113
+ // shown as a neutral placeholder rather than echoed, so it can neither leak a
114
+ // non-repo-relative path nor inject text. This display gate is intentionally
115
+ // stricter than the read resolver, which accepts any real repo-relative file.
116
+ const rawDesignMd = mode.designSystem?.designMd ?? "DESIGN.md";
117
+ const designMdRel = isStrictRepoRelativePath(rawDesignMd)
118
+ ? rawDesignMd
119
+ : "(configured path omitted: unsafe characters)";
120
+ const designDoc = designMdContent
121
+ ? `## DESIGN.md (design source of truth)\n\n${designMdContent}`
122
+ : `## DESIGN.md\n\nNo DESIGN.md was found at \`${designMdRel}\` — it has not been seeded yet. ` +
123
+ `Follow the repo's existing visual patterns strictly, and suggest running ` +
124
+ `\`df onboard\` to an engineer so design doctrine gets a home.`;
125
+ return [persona, summary, designDoc].join("\n\n---\n\n");
126
+ }
127
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/mode/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /** The critic id used for the ModeScope critic in `.agent-review/config.json`. */
2
+ export declare const MODE_SCOPE_CRITIC_ID = "mode-scope-critic";
3
+ export interface ModeScopeRegistrationResult {
4
+ /** Whether the config was modified (false = already registered / idempotent). */
5
+ changed: boolean;
6
+ /** Human-readable descriptions of each mutation, for `df mode init` output. */
7
+ changes: string[];
8
+ }
9
+ /**
10
+ * The critic entry appended to `critics[]`. Deterministic critic: no auth,
11
+ * no vendor SDK. `required: true` — the scope critic is a HARD GATE, so its
12
+ * blockers must block under EVERY aggregation policy. Under `block-if-any`
13
+ * the gate demotes `required: false` critics' findings to warnings (report.ts
14
+ * / gate.ts key on `config.critics.filter(c => c.required)`), so a
15
+ * `required: false` scope critic would NOT block an out-of-scope `Mode:` PR
16
+ * there. `required: true` is also safe under `min-complete-quorum`: that path
17
+ * (`quorumAggregateVerdict`) never consults the `required` flag, and because
18
+ * the adapter ALWAYS completes it never trips the "required critic errored →
19
+ * block" rule — so it only ever adds the veto its blocker already carries, and
20
+ * the per-path quorum bumps below stay exact. `runtime: "local"` is metadata —
21
+ * the same deterministic entry is referenced by both the local and cloud
22
+ * profiles (mirrors how `cursor-*` is shared across profiles).
23
+ */
24
+ export declare function buildModeScopeCriticEntry(): Record<string, unknown>;
25
+ /**
26
+ * Register the ModeScope critic into a raw parsed `.agent-review/config.json`
27
+ * object. Mutates `raw` in place AND returns a change report. The caller
28
+ * validates the mutated object through `parseAgentReviewConfig` before
29
+ * persisting it.
30
+ *
31
+ * "Present" is NOT "safe". A pre-existing mode-scope entry can itself be
32
+ * UNSAFE — an older CLI (or a hand edit) may have added it `required: false`
33
+ * (its blockers are demoted to warnings under block-if-any) or left it out of a
34
+ * profile (it does not run at all under profile-filtered runs). So rather than
35
+ * no-op the instant an entry is present, we CONVERGE it to the required shape:
36
+ * promote `required: true`, repair a missing id, and ensure the id is in every
37
+ * profile. Only a FULLY compliant entry is a true no-op (`changed: false`).
38
+ *
39
+ * Quorum is preserved EXACTLY ONCE per scope, keyed on the absent→present
40
+ * transition (`bumpRootQuorum` for the root fleet, `ensureCriticInEveryProfile`
41
+ * per profile), so a converge re-run never double-counts.
42
+ */
43
+ export declare function registerModeScopeCritic(raw: unknown): ModeScopeRegistrationResult;
44
+ export interface CriticRegistrationOutcome {
45
+ /** Resolved path to `.agent-review/config.json`. */
46
+ configPath: string;
47
+ status: "registered" | "already-registered" | "absent" | "error";
48
+ /** Change descriptions when status === "registered". */
49
+ changes: string[];
50
+ /** Present when status === "error". */
51
+ error?: string;
52
+ }
53
+ /**
54
+ * Register the ModeScope critic into `<repoRoot>/.agent-review/config.json`.
55
+ *
56
+ * - `absent`: no config.json — the consumer hasn't run `df onboard`. Skipped
57
+ * (not an error): the catalog seed still stands and re-running init after
58
+ * onboarding completes registration.
59
+ * - `already-registered`: the critic is already present — idempotent no-op.
60
+ * - `registered`: patched + written.
61
+ * - `error`: the config is malformed OR the patched config fails canonical
62
+ * validation. The on-disk file is left UNTOUCHED (fail-closed); the error
63
+ * is reported, not thrown, so `df mode init` degrades gracefully.
64
+ */
65
+ export declare function applyModeScopeCriticRegistration(repoRoot: string): CriticRegistrationOutcome;
66
+ //# sourceMappingURL=critic-registration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"critic-registration.d.ts","sourceRoot":"","sources":["../../src/mode/critic-registration.ts"],"names":[],"mappings":"AAwCA,kFAAkF;AAClF,eAAO,MAAM,oBAAoB,sBAAsB,CAAC;AAExD,MAAM,WAAW,2BAA2B;IAC1C,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;IACjB,+EAA+E;IAC/E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAMD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASnE;AAqED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,OAAO,GAAG,2BAA2B,CA4FjF;AAKD,MAAM,WAAW,yBAAyB;IACxC,oDAAoD;IACpD,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,GAAG,oBAAoB,GAAG,QAAQ,GAAG,OAAO,CAAC;IACjE,wDAAwD;IACxD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,uCAAuC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,gCAAgC,CAAC,QAAQ,EAAE,MAAM,GAAG,yBAAyB,CA4C5F"}
@@ -0,0 +1,279 @@
1
+ // Cycle 27 Phase C — register the deterministic ModeScope critic into a
2
+ // consumer's `.agent-review/config.json`.
3
+ //
4
+ // `df mode init` calls this after seeding the `modes` catalog so the scope
5
+ // critic RUNS in local `df review` / `df gate-push` AND in CI `agent-critic`
6
+ // (inside the existing required check — no consumer ruleset/workflow
7
+ // changes). The mirror of static-schema-lint's posture: the adapter ships
8
+ // available-but-off in `ADAPTER_LOADERS`; a config reference is what turns
9
+ // it on for a repo.
10
+ //
11
+ // QUORUM SAFETY (the subtle part). The scope critic is a deterministic,
12
+ // veto-only critic that ALWAYS completes. Under `min-complete-quorum`, the
13
+ // gate needs `quorum` critics to COMPLETE. Naively appending an
14
+ // always-completing critic to the fleet WITHOUT touching quorum would let
15
+ // it substitute for an LLM completion — silently LOWERING the number of LLM
16
+ // chief-engineers that must complete. `df critic` aggregates on TWO paths
17
+ // and the scope critic (added to `critics[]`) runs on BOTH, so BOTH quorums
18
+ // are bumped by exactly 1:
19
+ // - the root `aggregation.quorum`, which governs the PROFILE-LESS run the
20
+ // reusable workflow's CONSUMER path uses (`df critic` with no
21
+ // `AGENT_REVIEW_PROFILE` → `resolveCriticProfile` → undefined → whole
22
+ // `critics[]` fleet under the root aggregation); AND
23
+ // - every `profile.quorum`, for EXPLICIT-profile runs like dark-factory's
24
+ // own `AGENT_REVIEW_PROFILE=cloud` CI (filters to `profile.criticIds`).
25
+ // The bumps are independent — a single run uses exactly one aggregation
26
+ // config (root OR one profile) — so they never double-count. The engineer
27
+ // reviewing the init PR sees each bump reported in `df mode init`'s output.
28
+ //
29
+ // Operates on the RAW parsed JSON (not the normalized AgentReviewConfig) so
30
+ // consumer customizations are preserved byte-for-byte except the fields we
31
+ // touch. The caller re-validates the mutated object through
32
+ // `parseAgentReviewConfig` (fail-closed) before writing.
33
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
34
+ import { join } from "node:path";
35
+ import { parseAgentReviewConfig } from "@momentiq/dark-factory-schemas";
36
+ import { MODE_SCOPE_ADAPTER_ID } from "../adapters/mode-scope.js";
37
+ /** The critic id used for the ModeScope critic in `.agent-review/config.json`. */
38
+ export const MODE_SCOPE_CRITIC_ID = "mode-scope-critic";
39
+ function isRecord(v) {
40
+ return typeof v === "object" && v !== null && !Array.isArray(v);
41
+ }
42
+ /**
43
+ * The critic entry appended to `critics[]`. Deterministic critic: no auth,
44
+ * no vendor SDK. `required: true` — the scope critic is a HARD GATE, so its
45
+ * blockers must block under EVERY aggregation policy. Under `block-if-any`
46
+ * the gate demotes `required: false` critics' findings to warnings (report.ts
47
+ * / gate.ts key on `config.critics.filter(c => c.required)`), so a
48
+ * `required: false` scope critic would NOT block an out-of-scope `Mode:` PR
49
+ * there. `required: true` is also safe under `min-complete-quorum`: that path
50
+ * (`quorumAggregateVerdict`) never consults the `required` flag, and because
51
+ * the adapter ALWAYS completes it never trips the "required critic errored →
52
+ * block" rule — so it only ever adds the veto its blocker already carries, and
53
+ * the per-path quorum bumps below stay exact. `runtime: "local"` is metadata —
54
+ * the same deterministic entry is referenced by both the local and cloud
55
+ * profiles (mirrors how `cursor-*` is shared across profiles).
56
+ */
57
+ export function buildModeScopeCriticEntry() {
58
+ return {
59
+ id: MODE_SCOPE_CRITIC_ID,
60
+ name: "Mode Scope Critic — deterministic operator-mode scope enforcement",
61
+ adapter: MODE_SCOPE_ADAPTER_ID,
62
+ required: true,
63
+ runtime: "local",
64
+ model: { id: "deterministic", params: [] },
65
+ };
66
+ }
67
+ /**
68
+ * Bump the ROOT `aggregation.quorum` by 1 (min-complete-quorum only). Governs
69
+ * the PROFILE-LESS run: the reusable workflow's consumer path invokes
70
+ * `df critic` with no `AGENT_REVIEW_PROFILE`, so the whole `critics[]` fleet —
71
+ * now including the always-completing scope critic — is aggregated under the
72
+ * root policy, and its LLM completion floor must be preserved. Under
73
+ * block-if-any there is no quorum to preserve (a blocker vetoes directly), so
74
+ * nothing is bumped.
75
+ *
76
+ * Called EXACTLY ONCE, at the moment the scope critic is first ADDED to
77
+ * `critics[]` (a fresh registration) — never on a converge re-run. Quorum is a
78
+ * bare integer, so "already bumped to N" is indistinguishable from "should be
79
+ * N+1 but wasn't"; a converge that re-bumped would inflate the floor without
80
+ * bound. Presence of the entry in `critics[]` means the root fleet already
81
+ * accounts for it. (This is the exact-once transition key.)
82
+ */
83
+ function bumpRootQuorum(raw, changes) {
84
+ const aggregation = raw["aggregation"];
85
+ if (isRecord(aggregation) &&
86
+ aggregation["policy"] === "min-complete-quorum" &&
87
+ typeof aggregation["quorum"] === "number") {
88
+ const prev = aggregation["quorum"];
89
+ aggregation["quorum"] = prev + 1;
90
+ changes.push(`aggregation.quorum bumped ${prev}→${prev + 1} (governs profile-less runs incl. the reusable workflow's consumer path; preserves the LLM completion floor)`);
91
+ }
92
+ }
93
+ /**
94
+ * Ensure `criticId` is listed in EVERY profile's `criticIds`, bumping the
95
+ * quorum by 1 ONLY for each profile it was newly ADDED to. The add and the
96
+ * bump are coupled and keyed on the absent→present transition: a profile that
97
+ * already lists the critic is left untouched (no double-bump). This is what
98
+ * makes both fresh registration and the converge path idempotent — re-running
99
+ * `df mode init` never inflates a profile that already accounts for the scope
100
+ * critic, and it repairs a profile that a partial migration left it out of.
101
+ */
102
+ function ensureCriticInEveryProfile(raw, criticId, changes) {
103
+ const profiles = raw["profiles"];
104
+ if (!isRecord(profiles))
105
+ return;
106
+ for (const [name, profileRaw] of Object.entries(profiles)) {
107
+ if (!isRecord(profileRaw))
108
+ continue;
109
+ const ids = Array.isArray(profileRaw["criticIds"])
110
+ ? profileRaw["criticIds"]
111
+ : [];
112
+ if (ids.includes(criticId))
113
+ continue; // already accounted for — no re-bump.
114
+ ids.push(criticId);
115
+ profileRaw["criticIds"] = ids;
116
+ if (typeof profileRaw["quorum"] === "number") {
117
+ const prev = profileRaw["quorum"];
118
+ profileRaw["quorum"] = prev + 1;
119
+ changes.push(`profile "${name}": added scope critic + bumped quorum ${prev}→${prev + 1} (preserves the LLM completion floor)`);
120
+ }
121
+ else {
122
+ changes.push(`profile "${name}": added scope critic`);
123
+ }
124
+ }
125
+ }
126
+ /**
127
+ * Register the ModeScope critic into a raw parsed `.agent-review/config.json`
128
+ * object. Mutates `raw` in place AND returns a change report. The caller
129
+ * validates the mutated object through `parseAgentReviewConfig` before
130
+ * persisting it.
131
+ *
132
+ * "Present" is NOT "safe". A pre-existing mode-scope entry can itself be
133
+ * UNSAFE — an older CLI (or a hand edit) may have added it `required: false`
134
+ * (its blockers are demoted to warnings under block-if-any) or left it out of a
135
+ * profile (it does not run at all under profile-filtered runs). So rather than
136
+ * no-op the instant an entry is present, we CONVERGE it to the required shape:
137
+ * promote `required: true`, repair a missing id, and ensure the id is in every
138
+ * profile. Only a FULLY compliant entry is a true no-op (`changed: false`).
139
+ *
140
+ * Quorum is preserved EXACTLY ONCE per scope, keyed on the absent→present
141
+ * transition (`bumpRootQuorum` for the root fleet, `ensureCriticInEveryProfile`
142
+ * per profile), so a converge re-run never double-counts.
143
+ */
144
+ export function registerModeScopeCritic(raw) {
145
+ if (!isRecord(raw)) {
146
+ throw new Error("`.agent-review/config.json` is not a JSON object");
147
+ }
148
+ const critics = Array.isArray(raw["critics"])
149
+ ? raw["critics"]
150
+ : [];
151
+ raw["critics"] = critics;
152
+ const existing = critics.filter((c) => isRecord(c) && c["adapter"] === MODE_SCOPE_ADAPTER_ID);
153
+ if (existing.length > 1) {
154
+ // Ambiguous / corrupt: two always-completing scope critics double-count the
155
+ // quorum floor and there is no single canonical entry to converge. Fail
156
+ // CLOSED — the caller surfaces this as `error` and `df mode init` fails loud
157
+ // (never a silent "already-registered"). Judgment call: we throw rather than
158
+ // auto-dedup, because choosing which duplicate to keep and reconciling their
159
+ // differing required/id/profile state is not safely inferable.
160
+ throw new Error(`\`.agent-review/config.json\` has ${existing.length} critics with adapter ` +
161
+ `"${MODE_SCOPE_ADAPTER_ID}"; expected at most one. Remove the duplicate ` +
162
+ `entries and re-run \`df mode init\`.`);
163
+ }
164
+ const changes = [];
165
+ if (existing.length === 0) {
166
+ // FRESH registration: add the entry, then preserve the floor once on each
167
+ // scope it newly enters (the root fleet + every profile).
168
+ critics.push(buildModeScopeCriticEntry());
169
+ changes.push(`added critic "${MODE_SCOPE_CRITIC_ID}" (adapter: ${MODE_SCOPE_ADAPTER_ID}, required: true, deterministic hard gate)`);
170
+ bumpRootQuorum(raw, changes);
171
+ ensureCriticInEveryProfile(raw, MODE_SCOPE_CRITIC_ID, changes);
172
+ return { changed: true, changes };
173
+ }
174
+ // CONVERGE the single pre-existing entry to the required shape.
175
+ const entry = existing[0];
176
+ // 1. Canonical id. Profiles reference critics BY id, so keep an existing
177
+ // non-empty string id (forcing a new one would orphan those references)
178
+ // and only repair a missing / non-string one. Judgment call: we do NOT
179
+ // rename a differing existing id — it is the consumer's chosen id and
180
+ // renaming is not safely reversible; we converge profiles to whatever id
181
+ // the entry actually uses.
182
+ let criticId;
183
+ if (typeof entry["id"] === "string" && entry["id"].length > 0) {
184
+ criticId = entry["id"];
185
+ }
186
+ else {
187
+ entry["id"] = MODE_SCOPE_CRITIC_ID;
188
+ criticId = MODE_SCOPE_CRITIC_ID;
189
+ changes.push(`set the missing id on the existing mode-scope critic to "${MODE_SCOPE_CRITIC_ID}"`);
190
+ }
191
+ // 2. Promote to required: true — the hard-gate invariant. A required: false
192
+ // scope critic's blockers are demoted to warnings under block-if-any.
193
+ if (entry["required"] !== true) {
194
+ const was = entry["required"];
195
+ entry["required"] = true;
196
+ changes.push(`promoted the existing mode-scope critic "${criticId}" to required: true ` +
197
+ `(hard-gate invariant; was ${JSON.stringify(was)})`);
198
+ }
199
+ // 3. Ensure the id is in every profile (adds + bumps only the profiles that
200
+ // were missing it). Already-listing profiles are left alone — their
201
+ // presence means they already account for the critic.
202
+ //
203
+ // WONTFIX (documented, #311): the ROOT `aggregation.quorum` is likewise
204
+ // NOT re-bumped on converge. A hand-corrupted config where `mode-scope` is
205
+ // in `critics[]` but the root quorum was never bumped is OUTSIDE this
206
+ // converge contract's repair scope, for two reasons: (1) the CLI never
207
+ // produces that state — the fresh path bumps root atomically with the add
208
+ // (the caller writes the fully-mutated object or nothing), and no released
209
+ // CLI registers `mode-scope` at all, so it is reachable only by hand-edit;
210
+ // (2) root-bump status is UNOBSERVABLE from a bare integer — `N` cannot be
211
+ // told apart from `(N+1) minus one` — so any in-registration "repair" is a
212
+ // blind guess that either over-inflates the already-correct case or runs
213
+ // away on re-runs (breaking idempotency). Detection of this hand-edit-only
214
+ // drift is deferred to a read-only `df doctor` check (#333); registration
215
+ // stays idempotent.
216
+ ensureCriticInEveryProfile(raw, criticId, changes);
217
+ return { changed: changes.length > 0, changes };
218
+ }
219
+ /**
220
+ * Register the ModeScope critic into `<repoRoot>/.agent-review/config.json`.
221
+ *
222
+ * - `absent`: no config.json — the consumer hasn't run `df onboard`. Skipped
223
+ * (not an error): the catalog seed still stands and re-running init after
224
+ * onboarding completes registration.
225
+ * - `already-registered`: the critic is already present — idempotent no-op.
226
+ * - `registered`: patched + written.
227
+ * - `error`: the config is malformed OR the patched config fails canonical
228
+ * validation. The on-disk file is left UNTOUCHED (fail-closed); the error
229
+ * is reported, not thrown, so `df mode init` degrades gracefully.
230
+ */
231
+ export function applyModeScopeCriticRegistration(repoRoot) {
232
+ const configPath = join(repoRoot, ".agent-review", "config.json");
233
+ if (!existsSync(configPath)) {
234
+ return { configPath, status: "absent", changes: [] };
235
+ }
236
+ let raw;
237
+ try {
238
+ raw = JSON.parse(readFileSync(configPath, "utf8"));
239
+ }
240
+ catch (e) {
241
+ return {
242
+ configPath,
243
+ status: "error",
244
+ changes: [],
245
+ error: `not valid JSON: ${e instanceof Error ? e.message : String(e)}`,
246
+ };
247
+ }
248
+ let result;
249
+ try {
250
+ result = registerModeScopeCritic(raw); // mutates `raw` in place
251
+ }
252
+ catch (e) {
253
+ return {
254
+ configPath,
255
+ status: "error",
256
+ changes: [],
257
+ error: e instanceof Error ? e.message : String(e),
258
+ };
259
+ }
260
+ if (!result.changed) {
261
+ return { configPath, status: "already-registered", changes: [] };
262
+ }
263
+ // Fail closed: the mutated object MUST parse canonically before we persist
264
+ // it. On failure the on-disk file is never rewritten.
265
+ try {
266
+ parseAgentReviewConfig(raw);
267
+ }
268
+ catch (e) {
269
+ return {
270
+ configPath,
271
+ status: "error",
272
+ changes: [],
273
+ error: `patched config failed validation (not written): ${e instanceof Error ? e.message : String(e)}`,
274
+ };
275
+ }
276
+ writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`);
277
+ return { configPath, status: "registered", changes: result.changes };
278
+ }
279
+ //# sourceMappingURL=critic-registration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"critic-registration.js","sourceRoot":"","sources":["../../src/mode/critic-registration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import type { ModeConfig } from "@momentiq/dark-factory-schemas";
2
+ export { globMatch } from "./scope-policy.js";
3
+ export interface ResolvedModeLike {
4
+ modeName: string;
5
+ mode: ModeConfig;
6
+ }
7
+ export type GuardDecision = {
8
+ decision: "allow";
9
+ } | {
10
+ decision: "deny";
11
+ plain: string;
12
+ hint: string;
13
+ };
14
+ export declare function decideGuard(input: unknown, resolved: ResolvedModeLike | undefined): GuardDecision;
15
+ //# sourceMappingURL=guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../src/mode/guard.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAQjE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,EAAE,OAAO,CAAA;CAAE,GACrB;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAuItD,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,QAAQ,EAAE,gBAAgB,GAAG,SAAS,GACrC,aAAa,CAwCf"}