@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.
- package/dist/adapters/cursor-sdk.d.ts.map +1 -1
- package/dist/adapters/cursor-sdk.js +14 -2
- package/dist/adapters/cursor-sdk.js.map +1 -1
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +6 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/adapters/mode-scope.d.ts +86 -0
- package/dist/adapters/mode-scope.d.ts.map +1 -0
- package/dist/adapters/mode-scope.js +456 -0
- package/dist/adapters/mode-scope.js.map +1 -0
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +17 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/mode.d.ts.map +1 -1
- package/dist/commands/mode.js +135 -10
- package/dist/commands/mode.js.map +1 -1
- package/dist/commands/objectives.d.ts +27 -3
- package/dist/commands/objectives.d.ts.map +1 -1
- package/dist/commands/objectives.js +347 -53
- package/dist/commands/objectives.js.map +1 -1
- package/dist/cycle-doc-validator/validate_cycle_doc.py +128 -11
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +8 -4
- package/dist/doctor.js.map +1 -1
- package/dist/evidence/index.d.ts +1 -0
- package/dist/evidence/index.d.ts.map +1 -1
- package/dist/evidence/index.js +5 -0
- package/dist/evidence/index.js.map +1 -1
- package/dist/evidence/ui-visual/embed.d.ts +65 -0
- package/dist/evidence/ui-visual/embed.d.ts.map +1 -0
- package/dist/evidence/ui-visual/embed.js +176 -0
- package/dist/evidence/ui-visual/embed.js.map +1 -0
- package/dist/evidence/ui-visual/index.d.ts +2 -0
- package/dist/evidence/ui-visual/index.d.ts.map +1 -0
- package/dist/evidence/ui-visual/index.js +6 -0
- package/dist/evidence/ui-visual/index.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/cycle-doc/parser.d.ts +3 -3
- package/dist/mcp/cycle-doc/parser.d.ts.map +1 -1
- package/dist/mcp/cycle-doc/parser.js +2 -2
- package/dist/mcp/cycle-doc/parser.js.map +1 -1
- package/dist/mcp/tools/doctor.d.ts +5 -0
- package/dist/mcp/tools/doctor.d.ts.map +1 -1
- package/dist/mcp/tools/doctor.js +10 -1
- package/dist/mcp/tools/doctor.js.map +1 -1
- package/dist/mcp/tools/review-bypass.d.ts +5 -0
- package/dist/mcp/tools/review-bypass.d.ts.map +1 -1
- package/dist/mcp/tools/review-bypass.js +9 -1
- package/dist/mcp/tools/review-bypass.js.map +1 -1
- package/dist/mode/apply.d.ts +8 -0
- package/dist/mode/apply.d.ts.map +1 -0
- package/dist/mode/apply.js +118 -0
- package/dist/mode/apply.js.map +1 -0
- package/dist/mode/context.d.ts +50 -0
- package/dist/mode/context.d.ts.map +1 -0
- package/dist/mode/context.js +127 -0
- package/dist/mode/context.js.map +1 -0
- package/dist/mode/critic-registration.d.ts +66 -0
- package/dist/mode/critic-registration.d.ts.map +1 -0
- package/dist/mode/critic-registration.js +279 -0
- package/dist/mode/critic-registration.js.map +1 -0
- package/dist/mode/guard.d.ts +15 -0
- package/dist/mode/guard.d.ts.map +1 -0
- package/dist/mode/guard.js +160 -0
- package/dist/mode/guard.js.map +1 -0
- package/dist/mode/init.d.ts.map +1 -1
- package/dist/mode/init.js +14 -5
- package/dist/mode/init.js.map +1 -1
- package/dist/mode/loadConfig.d.ts +34 -0
- package/dist/mode/loadConfig.d.ts.map +1 -0
- package/dist/mode/loadConfig.js +38 -0
- package/dist/mode/loadConfig.js.map +1 -0
- package/dist/mode/prompts/designer-baseline.md +47 -0
- package/dist/mode/resolve.d.ts +1 -1
- package/dist/mode/resolve.d.ts.map +1 -1
- package/dist/mode/resolve.js +19 -3
- package/dist/mode/resolve.js.map +1 -1
- package/dist/mode/scope-policy.d.ts +41 -0
- package/dist/mode/scope-policy.d.ts.map +1 -0
- package/dist/mode/scope-policy.js +97 -0
- package/dist/mode/scope-policy.js.map +1 -0
- package/dist/mode/status.d.ts.map +1 -1
- package/dist/mode/status.js +2 -9
- package/dist/mode/status.js.map +1 -1
- package/dist/onboard/validate.d.ts +24 -6
- package/dist/onboard/validate.d.ts.map +1 -1
- package/dist/onboard/validate.js +87 -14
- package/dist/onboard/validate.js.map +1 -1
- package/dist/skills/config.d.ts +2 -113
- package/dist/skills/config.d.ts.map +1 -1
- package/dist/skills/config.js +19 -62
- package/dist/skills/config.js.map +1 -1
- package/dist/skills/index.d.ts +1 -1
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +1 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/install.d.ts.map +1 -1
- package/dist/skills/install.js +7 -0
- package/dist/skills/install.js.map +1 -1
- package/package.json +2 -2
- package/skills/designer-brief/SKILL.md.tmpl +74 -0
- package/skills/designer-brief/skill.json +17 -0
- package/skills/designer-build/SKILL.md.tmpl +63 -0
- package/skills/designer-build/skill.json +17 -0
- package/skills/designer-handoff/SKILL.md.tmpl +64 -0
- package/skills/designer-handoff/skill.json +17 -0
- package/skills/designer-ship/SKILL.md.tmpl +77 -0
- package/skills/designer-ship/skill.json +17 -0
- package/skills/objectives/SKILL.md.tmpl +16 -1
- package/skills/verify/producer/ui-visual-surfaces.ts +261 -0
- package/skills/verify/producer/ui-visual.config.ts +22 -0
- 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"}
|