@momentiq/dark-factory-cli 2.17.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/codex-sdk.d.ts +30 -5
- package/dist/adapters/codex-sdk.d.ts.map +1 -1
- package/dist/adapters/codex-sdk.js +50 -15
- package/dist/adapters/codex-sdk.js.map +1 -1
- 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/branch-protection/audit_branch_protection.py +80 -2
- package/dist/cli.d.ts +5 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +31 -1
- package/dist/cli.js.map +1 -1
- package/dist/commands/mode.d.ts +2 -0
- package/dist/commands/mode.d.ts.map +1 -0
- package/dist/commands/mode.js +235 -0
- package/dist/commands/mode.js.map +1 -0
- 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/commands/onboard.d.ts.map +1 -1
- package/dist/commands/onboard.js +3 -1
- package/dist/commands/onboard.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 +15 -7
- 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 +17 -0
- package/dist/mode/init.d.ts.map +1 -0
- package/dist/mode/init.js +147 -0
- package/dist/mode/init.js.map +1 -0
- 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 +24 -0
- package/dist/mode/resolve.d.ts.map +1 -0
- package/dist/mode/resolve.js +65 -0
- package/dist/mode/resolve.js.map +1 -0
- 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 +14 -0
- package/dist/mode/status.d.ts.map +1 -0
- package/dist/mode/status.js +13 -0
- package/dist/mode/status.js.map +1 -0
- package/dist/onboard/seeders/design-md.d.ts +3 -0
- package/dist/onboard/seeders/design-md.d.ts.map +1 -0
- package/dist/onboard/seeders/design-md.js +138 -0
- package/dist/onboard/seeders/design-md.js.map +1 -0
- package/dist/onboard/seeders/index.d.ts +8 -0
- package/dist/onboard/seeders/index.d.ts.map +1 -1
- package/dist/onboard/seeders/index.js +3 -0
- package/dist/onboard/seeders/index.js.map +1 -1
- package/dist/onboard/validate.d.ts +36 -1
- package/dist/onboard/validate.d.ts.map +1 -1
- package/dist/onboard/validate.js +136 -42
- package/dist/onboard/validate.js.map +1 -1
- package/dist/skills/config.d.ts +2 -110
- package/dist/skills/config.d.ts.map +1 -1
- package/dist/skills/config.js +20 -58
- 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 +3 -3
- 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 +34 -6
- 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,118 @@
|
|
|
1
|
+
// Cycle 27 Phase B — `df mode apply <name>`: generate the per-user Claude
|
|
2
|
+
// Code overlay in `.claude/settings.local.json` (git-ignored — engineers on
|
|
3
|
+
// the same repo are untouched; the designer opts in on their own machine).
|
|
4
|
+
//
|
|
5
|
+
// Merge discipline: read-modify-write JSON that only ADDS what the mode
|
|
6
|
+
// needs — env.DF_MODE, the two df hooks, a destructive-git deny belt, and
|
|
7
|
+
// MCP enablement — and never disturbs unrelated user keys. Idempotent by
|
|
8
|
+
// containment checks, not by rewriting.
|
|
9
|
+
import { execFileSync } from "node:child_process";
|
|
10
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
import { parse as parseYaml } from "yaml";
|
|
13
|
+
import { parseDarkFactoryConfig } from "@momentiq/dark-factory-schemas";
|
|
14
|
+
import { resolveModeWithConfig } from "./resolve.js";
|
|
15
|
+
const GUARD_COMMAND = '"$CLAUDE_PROJECT_DIR"/node_modules/.bin/df mode guard --hook';
|
|
16
|
+
const CONTEXT_COMMAND = '"$CLAUDE_PROJECT_DIR"/node_modules/.bin/df mode context --hook';
|
|
17
|
+
const DENY_BELT = [
|
|
18
|
+
"Bash(git push --force:*)",
|
|
19
|
+
"Bash(git push -f:*)",
|
|
20
|
+
"Bash(git reset --hard:*)",
|
|
21
|
+
"Bash(git clean:*)",
|
|
22
|
+
];
|
|
23
|
+
function ensureHook(hooks, event, command, matcher) {
|
|
24
|
+
const entries = (hooks[event] ??= []);
|
|
25
|
+
const present = entries.some((e) => e.hooks.some((h) => h.command === command));
|
|
26
|
+
if (present)
|
|
27
|
+
return;
|
|
28
|
+
const entry = { hooks: [{ type: "command", command }] };
|
|
29
|
+
if (matcher !== undefined)
|
|
30
|
+
entry.matcher = matcher;
|
|
31
|
+
entries.push(entry);
|
|
32
|
+
}
|
|
33
|
+
export function applyMode(repoRoot, modeName) {
|
|
34
|
+
// Validate against the catalog first — applying an unknown mode would
|
|
35
|
+
// produce a session that immediately fail-closes every file tool.
|
|
36
|
+
const configPath = join(repoRoot, "darkfactory.yaml");
|
|
37
|
+
const raw = existsSync(configPath)
|
|
38
|
+
? (parseYaml(readFileSync(configPath, "utf8")) ?? {})
|
|
39
|
+
: {};
|
|
40
|
+
resolveModeWithConfig(parseDarkFactoryConfig(raw), modeName);
|
|
41
|
+
const dir = join(repoRoot, ".claude");
|
|
42
|
+
const settingsPath = join(dir, "settings.local.json");
|
|
43
|
+
const created = !existsSync(settingsPath);
|
|
44
|
+
const settings = created
|
|
45
|
+
? {}
|
|
46
|
+
: JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
47
|
+
settings.env = { ...(settings.env ?? {}), DF_MODE: modeName };
|
|
48
|
+
const hooks = (settings.hooks ??= {});
|
|
49
|
+
ensureHook(hooks, "PreToolUse", GUARD_COMMAND, "Edit|Write|MultiEdit|NotebookEdit|Bash");
|
|
50
|
+
ensureHook(hooks, "SessionStart", CONTEXT_COMMAND);
|
|
51
|
+
const permissions = (settings.permissions ??= {});
|
|
52
|
+
const deny = new Set(permissions.deny ?? []);
|
|
53
|
+
for (const rule of DENY_BELT)
|
|
54
|
+
deny.add(rule);
|
|
55
|
+
permissions.deny = [...deny];
|
|
56
|
+
const servers = new Set(settings.enabledMcpjsonServers ?? []);
|
|
57
|
+
servers.add("dark-factory");
|
|
58
|
+
settings.enabledMcpjsonServers = [...servers];
|
|
59
|
+
mkdirSync(dir, { recursive: true });
|
|
60
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
|
|
61
|
+
ensureLocallyIgnored(repoRoot);
|
|
62
|
+
return { status: "applied", modeName, settingsPath, created };
|
|
63
|
+
}
|
|
64
|
+
const OVERLAY_IGNORE_ENTRY = ".claude/settings.local.json";
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the git **common** directory for `repoRoot` — the directory that
|
|
67
|
+
* owns `info/exclude`. In a normal clone this is `<repoRoot>/.git`, but in a
|
|
68
|
+
* linked worktree (or a submodule) `<repoRoot>/.git` is a POINTER FILE
|
|
69
|
+
* (`gitdir: …`), not a directory, and the per-clone exclude lives in the
|
|
70
|
+
* SHARED common dir, never under the worktree-private git dir. `git rev-parse
|
|
71
|
+
* --git-common-dir` reports the right path in every layout — absolute for a
|
|
72
|
+
* linked worktree, relative (`.git`) for a normal clone — and `resolve`
|
|
73
|
+
* returns an absolute segment as-is while joining the relative one onto
|
|
74
|
+
* `repoRoot`, so a single call covers both. Best-effort: returns null when git
|
|
75
|
+
* is unavailable or `repoRoot` is not a work tree, so apply skips ignoring
|
|
76
|
+
* rather than crashing.
|
|
77
|
+
*/
|
|
78
|
+
function resolveGitCommonDir(repoRoot) {
|
|
79
|
+
try {
|
|
80
|
+
const out = execFileSync("git", ["rev-parse", "--git-common-dir"], {
|
|
81
|
+
cwd: repoRoot,
|
|
82
|
+
encoding: "utf8",
|
|
83
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
84
|
+
}).trim();
|
|
85
|
+
return out.length === 0 ? null : resolve(repoRoot, out);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The overlay is documented as per-user and git-ignored. Claude Code's own
|
|
93
|
+
* scaffolds usually ignore it, but nothing guarantees a given consumer repo
|
|
94
|
+
* does — so make it true structurally via the git common dir's `info/exclude`,
|
|
95
|
+
* which is per-clone (never committed), exactly matching the overlay's per-user
|
|
96
|
+
* semantics. Best-effort: a non-git directory — or a linked worktree whose git
|
|
97
|
+
* dir cannot be resolved — is left untouched, never crashing apply.
|
|
98
|
+
*/
|
|
99
|
+
function ensureLocallyIgnored(repoRoot) {
|
|
100
|
+
// Fast local guard: only act when THIS directory roots a work tree (`.git`
|
|
101
|
+
// is a dir in a normal clone, a pointer FILE in a linked worktree/submodule).
|
|
102
|
+
// Skipping the git probe for a plain directory keeps apply best-effort and
|
|
103
|
+
// stops git's parent search from writing to an unrelated ancestor repo.
|
|
104
|
+
if (!existsSync(join(repoRoot, ".git")))
|
|
105
|
+
return;
|
|
106
|
+
const gitCommonDir = resolveGitCommonDir(repoRoot);
|
|
107
|
+
if (gitCommonDir === null)
|
|
108
|
+
return;
|
|
109
|
+
const infoDir = join(gitCommonDir, "info");
|
|
110
|
+
const excludePath = join(infoDir, "exclude");
|
|
111
|
+
const current = existsSync(excludePath) ? readFileSync(excludePath, "utf8") : "";
|
|
112
|
+
if (current.split(/\r?\n/).includes(OVERLAY_IGNORE_ENTRY))
|
|
113
|
+
return;
|
|
114
|
+
mkdirSync(infoDir, { recursive: true });
|
|
115
|
+
const separator = current.length === 0 || current.endsWith("\n") ? "" : "\n";
|
|
116
|
+
writeFileSync(excludePath, `${current}${separator}${OVERLAY_IGNORE_ENTRY}\n`);
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=apply.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/mode/apply.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,4EAA4E;AAC5E,2EAA2E;AAC3E,EAAE;AACF,wEAAwE;AACxE,0EAA0E;AAC1E,yEAAyE;AACzE,wCAAwC;AAExC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAExE,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AASrD,MAAM,aAAa,GACjB,8DAA8D,CAAC;AACjE,MAAM,eAAe,GACnB,gEAAgE,CAAC;AAEnE,MAAM,SAAS,GAAG;IAChB,0BAA0B;IAC1B,qBAAqB;IACrB,0BAA0B;IAC1B,mBAAmB;CACpB,CAAC;AAcF,SAAS,UAAU,CACjB,KAAkC,EAClC,KAAa,EACb,OAAe,EACf,OAAgB;IAEhB,MAAM,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC;IAChF,IAAI,OAAO;QAAE,OAAO;IACpB,MAAM,KAAK,GAAc,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACnE,IAAI,OAAO,KAAK,SAAS;QAAE,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;IACnD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,QAAgB;IAC1D,sEAAsE;IACtE,kEAAkE;IAClE,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,kBAAkB,CAAC,CAAC;IACtD,MAAM,GAAG,GAAY,UAAU,CAAC,UAAU,CAAC;QACzC,CAAC,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACrD,CAAC,CAAC,EAAE,CAAC;IACP,qBAAqB,CAAC,sBAAsB,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,CAAC;IAE7D,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC;IACtD,MAAM,OAAO,GAAG,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAa,OAAO;QAChC,CAAC,CAAC,EAAE;QACJ,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAc,CAAC;IAEjE,QAAQ,CAAC,GAAG,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IAE9D,MAAM,KAAK,GAAG,CAAC,QAAQ,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IACtC,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,wCAAwC,CAAC,CAAC;IACzF,UAAU,CAAC,KAAK,EAAE,cAAc,EAAE,eAAe,CAAC,CAAC;IAEnD,MAAM,WAAW,GAAG,CAAC,QAAQ,CAAC,WAAW,KAAK,EAAE,CAAC,CAAC;IAClD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC7C,KAAK,MAAM,IAAI,IAAI,SAAS;QAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7C,WAAW,CAAC,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAE7B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,qBAAqB,IAAI,EAAE,CAAC,CAAC;IAC9D,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC5B,QAAQ,CAAC,qBAAqB,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC;IAE9C,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpC,aAAa,CAAC,YAAY,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;IACtE,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAC/B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,oBAAoB,GAAG,6BAA6B,CAAC;AAE3D;;;;;;;;;;;;GAYG;AACH,SAAS,mBAAmB,CAAC,QAAgB;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAAE;YACjE,GAAG,EAAE,QAAQ;YACb,QAAQ,EAAE,MAAM;YAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;SACpC,CAAC,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,GAAG,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC1D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,oBAAoB,CAAC,QAAgB;IAC5C,2EAA2E;IAC3E,8EAA8E;IAC9E,2EAA2E;IAC3E,wEAAwE;IACxE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAAE,OAAO;IAChD,MAAM,YAAY,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACnD,IAAI,YAAY,KAAK,IAAI;QAAE,OAAO;IAClC,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC3C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC7C,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACjF,IAAI,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,oBAAoB,CAAC;QAAE,OAAO;IAClE,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7E,aAAa,CAAC,WAAW,EAAE,GAAG,OAAO,GAAG,SAAS,GAAG,oBAAoB,IAAI,CAAC,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ModeConfig } from "@momentiq/dark-factory-schemas";
|
|
2
|
+
/**
|
|
3
|
+
* Load the baseline persona asset. Lives alongside this module in both src
|
|
4
|
+
* (tests) and dist (runtime — the build's copy-assets step ships it), so an
|
|
5
|
+
* import.meta.url-relative read works in both.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadBaselinePersona(): string;
|
|
8
|
+
export interface ResolvedModeLike {
|
|
9
|
+
modeName: string;
|
|
10
|
+
mode: ModeConfig;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The strict repo-relative path predicate used for the DISPLAY path only —
|
|
14
|
+
* pure lexical, no filesystem access. `buildModeContext` echoes the configured
|
|
15
|
+
* `designMd` into the LLM-consumed SessionStart note, so it gates on this and
|
|
16
|
+
* shows a neutral placeholder for anything that is not a genuine strict
|
|
17
|
+
* repo-relative path. True only for a non-empty, length-bounded value built
|
|
18
|
+
* from safe path characters (letters, digits, `.` `_` `-` `/`) that is NOT
|
|
19
|
+
* absolute and contains NO empty/current/parent (`.`/`..`) segments. The
|
|
20
|
+
* character grammar keeps the value free of spaces, newlines, backticks, and
|
|
21
|
+
* markdown metacharacters, so a hostile catalog value cannot express multi-word
|
|
22
|
+
* instructions or open a new markdown block in the injected context.
|
|
23
|
+
*
|
|
24
|
+
* This is deliberately STRICTER than what `safeDesignMdPath` accepts for reads:
|
|
25
|
+
* read-safety asks "is this a real file inside the repo?" (spaces are fine);
|
|
26
|
+
* display-safety asks "is this safe to echo into model context?" (they are not).
|
|
27
|
+
* Keeping them separate avoids narrowing the public `designSystem.designMd`
|
|
28
|
+
* string contract while still hardening the injected note.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isStrictRepoRelativePath(p: string): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the catalog's `designSystem.designMd` value safely: it is a
|
|
33
|
+
* REPO-RELATIVE path by contract, and this hook injects the file's contents
|
|
34
|
+
* into session context — so the check must hold against the REAL filesystem,
|
|
35
|
+
* not just lexically. Returns the resolved path only when:
|
|
36
|
+
* - the value is relative and its lexical resolution stays under repoRoot,
|
|
37
|
+
* - the file exists and is a regular file, and
|
|
38
|
+
* - its realpath (symlinks followed) still lives under the repo's realpath
|
|
39
|
+
* (a repo-local DESIGN.md symlinked to ~/.ssh/id_rsa returns null).
|
|
40
|
+
* A missing file also returns null — "unseeded" is a normal state the
|
|
41
|
+
* caller narrates; it must never be distinguishable from "blocked" in ways
|
|
42
|
+
* that leak what exists outside the repo.
|
|
43
|
+
*
|
|
44
|
+
* This accepts ANY real repo-relative file (paths with spaces included): it
|
|
45
|
+
* enforces filesystem safety, NOT the stricter display grammar, so it does not
|
|
46
|
+
* narrow the public `designSystem.designMd` string contract.
|
|
47
|
+
*/
|
|
48
|
+
export declare function safeDesignMdPath(repoRoot: string, designMd: string): string | null;
|
|
49
|
+
export declare function buildModeContext(resolved: ResolvedModeLike, designMdContent: string | undefined): string;
|
|
50
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/mode/context.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAIjE;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;CAClB;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAQ3D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAoBlF;AAED,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,gBAAgB,EAC1B,eAAe,EAAE,MAAM,GAAG,SAAS,GAClC,MAAM,CAqCR"}
|
|
@@ -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"}
|