@momentiq/dark-factory-cli 2.18.0 → 3.0.1

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 (119) hide show
  1. package/README.md +33 -15
  2. package/dist/adapters/cursor-sdk.d.ts.map +1 -1
  3. package/dist/adapters/cursor-sdk.js +14 -2
  4. package/dist/adapters/cursor-sdk.js.map +1 -1
  5. package/dist/adapters/index.d.ts +1 -0
  6. package/dist/adapters/index.d.ts.map +1 -1
  7. package/dist/adapters/index.js +6 -0
  8. package/dist/adapters/index.js.map +1 -1
  9. package/dist/adapters/mode-scope.d.ts +86 -0
  10. package/dist/adapters/mode-scope.d.ts.map +1 -0
  11. package/dist/adapters/mode-scope.js +456 -0
  12. package/dist/adapters/mode-scope.js.map +1 -0
  13. package/dist/branch-protection/audit_branch_protection.py +152 -26
  14. package/dist/cli.d.ts +5 -0
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +17 -1
  17. package/dist/cli.js.map +1 -1
  18. package/dist/commands/mode.d.ts.map +1 -1
  19. package/dist/commands/mode.js +135 -10
  20. package/dist/commands/mode.js.map +1 -1
  21. package/dist/commands/objectives.d.ts +27 -3
  22. package/dist/commands/objectives.d.ts.map +1 -1
  23. package/dist/commands/objectives.js +347 -53
  24. package/dist/commands/objectives.js.map +1 -1
  25. package/dist/cycle-doc-validator/validate_cycle_doc.py +128 -11
  26. package/dist/doctor.d.ts.map +1 -1
  27. package/dist/doctor.js +8 -4
  28. package/dist/doctor.js.map +1 -1
  29. package/dist/evidence/index.d.ts +1 -0
  30. package/dist/evidence/index.d.ts.map +1 -1
  31. package/dist/evidence/index.js +5 -0
  32. package/dist/evidence/index.js.map +1 -1
  33. package/dist/evidence/ui-visual/embed.d.ts +65 -0
  34. package/dist/evidence/ui-visual/embed.d.ts.map +1 -0
  35. package/dist/evidence/ui-visual/embed.js +176 -0
  36. package/dist/evidence/ui-visual/embed.js.map +1 -0
  37. package/dist/evidence/ui-visual/index.d.ts +2 -0
  38. package/dist/evidence/ui-visual/index.d.ts.map +1 -0
  39. package/dist/evidence/ui-visual/index.js +6 -0
  40. package/dist/evidence/ui-visual/index.js.map +1 -0
  41. package/dist/index.d.ts +1 -1
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +1 -1
  44. package/dist/index.js.map +1 -1
  45. package/dist/mcp/cycle-doc/parser.d.ts +3 -3
  46. package/dist/mcp/cycle-doc/parser.d.ts.map +1 -1
  47. package/dist/mcp/cycle-doc/parser.js +2 -2
  48. package/dist/mcp/cycle-doc/parser.js.map +1 -1
  49. package/dist/mcp/tools/doctor.d.ts +5 -0
  50. package/dist/mcp/tools/doctor.d.ts.map +1 -1
  51. package/dist/mcp/tools/doctor.js +10 -1
  52. package/dist/mcp/tools/doctor.js.map +1 -1
  53. package/dist/mcp/tools/review-bypass.d.ts +5 -0
  54. package/dist/mcp/tools/review-bypass.d.ts.map +1 -1
  55. package/dist/mcp/tools/review-bypass.js +9 -1
  56. package/dist/mcp/tools/review-bypass.js.map +1 -1
  57. package/dist/mode/apply.d.ts +8 -0
  58. package/dist/mode/apply.d.ts.map +1 -0
  59. package/dist/mode/apply.js +118 -0
  60. package/dist/mode/apply.js.map +1 -0
  61. package/dist/mode/context.d.ts +50 -0
  62. package/dist/mode/context.d.ts.map +1 -0
  63. package/dist/mode/context.js +127 -0
  64. package/dist/mode/context.js.map +1 -0
  65. package/dist/mode/critic-registration.d.ts +66 -0
  66. package/dist/mode/critic-registration.d.ts.map +1 -0
  67. package/dist/mode/critic-registration.js +279 -0
  68. package/dist/mode/critic-registration.js.map +1 -0
  69. package/dist/mode/guard.d.ts +15 -0
  70. package/dist/mode/guard.d.ts.map +1 -0
  71. package/dist/mode/guard.js +160 -0
  72. package/dist/mode/guard.js.map +1 -0
  73. package/dist/mode/init.d.ts.map +1 -1
  74. package/dist/mode/init.js +14 -5
  75. package/dist/mode/init.js.map +1 -1
  76. package/dist/mode/loadConfig.d.ts +34 -0
  77. package/dist/mode/loadConfig.d.ts.map +1 -0
  78. package/dist/mode/loadConfig.js +38 -0
  79. package/dist/mode/loadConfig.js.map +1 -0
  80. package/dist/mode/prompts/designer-baseline.md +47 -0
  81. package/dist/mode/resolve.d.ts +1 -1
  82. package/dist/mode/resolve.d.ts.map +1 -1
  83. package/dist/mode/resolve.js +19 -3
  84. package/dist/mode/resolve.js.map +1 -1
  85. package/dist/mode/scope-policy.d.ts +41 -0
  86. package/dist/mode/scope-policy.d.ts.map +1 -0
  87. package/dist/mode/scope-policy.js +97 -0
  88. package/dist/mode/scope-policy.js.map +1 -0
  89. package/dist/mode/status.d.ts.map +1 -1
  90. package/dist/mode/status.js +2 -9
  91. package/dist/mode/status.js.map +1 -1
  92. package/dist/onboard/validate.d.ts +24 -6
  93. package/dist/onboard/validate.d.ts.map +1 -1
  94. package/dist/onboard/validate.js +87 -14
  95. package/dist/onboard/validate.js.map +1 -1
  96. package/dist/skills/config.d.ts +2 -113
  97. package/dist/skills/config.d.ts.map +1 -1
  98. package/dist/skills/config.js +19 -62
  99. package/dist/skills/config.js.map +1 -1
  100. package/dist/skills/index.d.ts +1 -1
  101. package/dist/skills/index.d.ts.map +1 -1
  102. package/dist/skills/index.js +1 -1
  103. package/dist/skills/index.js.map +1 -1
  104. package/dist/skills/install.d.ts.map +1 -1
  105. package/dist/skills/install.js +7 -0
  106. package/dist/skills/install.js.map +1 -1
  107. package/package.json +2 -2
  108. package/skills/designer-brief/SKILL.md.tmpl +74 -0
  109. package/skills/designer-brief/skill.json +17 -0
  110. package/skills/designer-build/SKILL.md.tmpl +63 -0
  111. package/skills/designer-build/skill.json +17 -0
  112. package/skills/designer-handoff/SKILL.md.tmpl +64 -0
  113. package/skills/designer-handoff/skill.json +17 -0
  114. package/skills/designer-ship/SKILL.md.tmpl +77 -0
  115. package/skills/designer-ship/skill.json +17 -0
  116. package/skills/objectives/SKILL.md.tmpl +16 -1
  117. package/skills/verify/producer/ui-visual-surfaces.ts +261 -0
  118. package/skills/verify/producer/ui-visual.config.ts +22 -0
  119. package/skills/verify/producer/ui-visual.producer.spec.ts +137 -0
@@ -0,0 +1,160 @@
1
+ // Cycle 27 Phase B — `df mode guard --hook` pure decision core.
2
+ //
3
+ // The Designer Mode tool guard, bound to Claude Code's PreToolUse hook by
4
+ // commands/mode.ts (stdin JSON → decideGuard → hook decision JSON). Layer 2
5
+ // of the enforcement stack (spec §4.4): soft relative to the pre-push gate
6
+ // and CI ModeScope critic (layers 3–4), but the layer that turns a wrong
7
+ // turn into instant, plain-language feedback inside the session.
8
+ //
9
+ // Posture:
10
+ // - No active mode → allow everything: the guard must be invisible to
11
+ // normal engineer sessions.
12
+ // - Active mode → file-writing tools are scope-checked (protectedPaths
13
+ // WINS over writeScopes on overlap); `dependencyPolicy: "none"` denies
14
+ // manifest/lockfile edits and dependency-adding Bash; destructive git
15
+ // is denied; read-only tools pass.
16
+ // - Active mode + input we cannot understand → fail CLOSED. A guard that
17
+ // fails open under confusion is not a guard.
18
+ //
19
+ // Denials are dual-register: `plain` speaks to a non-technical operator
20
+ // (no jargon, no file-path vocabulary beyond the path itself); `hint`
21
+ // speaks to the agent (which config knob fired, what to do instead —
22
+ // usually: offer a handoff, never retry the same write).
23
+ import { relative, resolve, sep } from "node:path";
24
+ import { classifyModePath } from "./scope-policy.js";
25
+ // `globMatch` historically lived in this file and is imported from here by
26
+ // tests + external callers. Its canonical home is now scope-policy.ts
27
+ // (shared with the CI scope critic so layers 2 and 4 enforce one policy —
28
+ // spec §4.4); re-export keeps the old import path working.
29
+ export { globMatch } from "./scope-policy.js";
30
+ const ALLOW = { decision: "allow" };
31
+ const FILE_TOOLS = new Set(["Edit", "Write", "MultiEdit", "NotebookEdit"]);
32
+ const DESTRUCTIVE_BASH = [
33
+ { re: /\bgit\s+push\b[^\n;|&]*\s(--force\b|-f\b|--force-with-lease\b)/, what: "force-push" },
34
+ { re: /\bgit\s+reset\s+--hard\b/, what: "hard reset" },
35
+ { re: /\bgit\s+clean\s+-[a-zA-Z]*f/, what: "git clean" },
36
+ { re: /\brm\s+-[a-zA-Z]*r[a-zA-Z]*f|\brm\s+-[a-zA-Z]*f[a-zA-Z]*r/, what: "recursive delete" },
37
+ ];
38
+ const DEPENDENCY_BASH = /\b(npm\s+(install|i|add)\s+\S|yarn\s+add\b|pnpm\s+(add|install)\s+\S|bun\s+add\b)/;
39
+ // Bash is DEFAULT-DENY while a mode is active: shell writes (`> file`,
40
+ // `sed -i`, `cp`, arbitrary interpreters) would bypass writeScopes entirely,
41
+ // and no denylist can enumerate a shell. Only read-only inspection and the
42
+ // repo's own dev/test scripts are allowed — and only as single, unchained
43
+ // commands (metacharacters disqualify even safe-list prefixes).
44
+ const BASH_METACHARS = /[;&|<>`$\\\n\r]/;
45
+ const BASH_ALLOWLIST = [
46
+ /^git (status|log|diff|show|branch)(\s|$)/,
47
+ // NOTE: no `find` — its -delete/-exec/-ok forms mutate; the agent has
48
+ // dedicated read-only search tools instead.
49
+ /^(ls|pwd|cat|head|tail|grep|rg|echo|which)(\s|$)/,
50
+ /^npm (test|start)$/,
51
+ // `npm run <script>` (+ optional `--workspace=<name>`): RATIFIED trusted, same
52
+ // basis as `mode.devServer.command` below — package scripts are repo-author-
53
+ // committed. Contingent on package.json being operator-non-editable in these
54
+ // modes: `checkPath` denies manifest edits under `dependencyPolicy: "none"` and
55
+ // package.json sits in the seeded `protectedPaths`, so an operator cannot add a
56
+ // script — the reachable set is exactly what engineers vetted at commit time.
57
+ // No per-mode `allowedScripts` allowlist: it would duplicate those author-
58
+ // controls and desync from real scripts without narrowing an already author-
59
+ // bounded surface. The script-name charset admits no args and BASH_METACHARS is
60
+ // rejected first (checkBash), so there is no `-- <shell>` passthrough or
61
+ // chaining. Revisit only if a mode ever leaves package.json operator-editable.
62
+ /^npm run [A-Za-z0-9:_./-]+(\s--workspace=[A-Za-z0-9@/._-]+)?$/,
63
+ /^(node|npm|npx|git) --version$/,
64
+ ];
65
+ function deny(plain, hint) {
66
+ return { decision: "deny", plain, hint };
67
+ }
68
+ function failClosed(modeName) {
69
+ return deny(`To be safe, ${modeName} mode blocked an action it couldn't understand.`, `guard received unparseable PreToolUse input while mode "${modeName}" is active — fail-closed; re-issue the tool call with well-formed input or hand off`);
70
+ }
71
+ /**
72
+ * Repo-relative, forward-slash, `..`-free path — or null when it escapes the
73
+ * repo. BOTH absolute and relative inputs are canonicalized through
74
+ * `resolve(cwd, filePath)` first: a relative path with `..` segments
75
+ * ("src/components/../../server/db.ts") must be scope-checked against what
76
+ * it RESOLVES to, never against its raw text.
77
+ */
78
+ function relativize(filePath, cwd) {
79
+ const rel = relative(cwd, resolve(cwd, filePath));
80
+ const normalized = rel.split(sep).join("/");
81
+ if (normalized === "" || normalized === "." || normalized.startsWith("../") || normalized === "..") {
82
+ return null;
83
+ }
84
+ return normalized;
85
+ }
86
+ function checkPath(path, resolved) {
87
+ const { modeName, mode } = resolved;
88
+ // Precedence (protected > dependency > out-of-scope) lives in the shared
89
+ // classifier so this hook (layer 2) and the CI scope critic (layer 4)
90
+ // cannot drift — spec §4.4. This function maps the classification to the
91
+ // guard's dual-register deny messages.
92
+ const classification = classifyModePath(path, mode);
93
+ switch (classification.kind) {
94
+ case "protected":
95
+ return deny(`"${path}" is a protected area that needs an engineer — ${modeName} mode can't change it.`, `path matches mode "${modeName}" protectedPaths (protected wins over writeScopes) — do not retry; offer a handoff (df handoff) describing the intended change`);
96
+ case "dependency":
97
+ return deny(`Adding or changing installed packages isn't something ${modeName} mode can do.`, `dependencyPolicy is "none" for mode "${modeName}" — dependency changes escalate to an engineer via handoff`);
98
+ case "out-of-scope":
99
+ return deny(`"${path}" is outside the design areas set up for you, so ${modeName} mode left it unchanged.`, `path matches no writeScopes entry for mode "${modeName}" — if the task genuinely needs this file, stop and offer a handoff instead of retrying`);
100
+ case "allow":
101
+ return ALLOW;
102
+ }
103
+ }
104
+ function checkBash(command, resolved) {
105
+ const { modeName, mode } = resolved;
106
+ const trimmed = command.trim();
107
+ // Specific denials first — they carry sharper reasons than the default.
108
+ for (const { re, what } of DESTRUCTIVE_BASH) {
109
+ if (re.test(trimmed)) {
110
+ return deny(`That command could destroy work (${what}), so ${modeName} mode blocked it.`, `destructive command class "${what}" is always denied in operator modes — never needed for design work`);
111
+ }
112
+ }
113
+ if (mode.dependencyPolicy === "none" && DEPENDENCY_BASH.test(trimmed)) {
114
+ return deny(`Installing new packages isn't something ${modeName} mode can do.`, `dependencyPolicy is "none" for mode "${modeName}" — dependency additions escalate via handoff`);
115
+ }
116
+ // The catalog's own dev-server command is repo-author-committed — trusted
117
+ // verbatim (exact match only).
118
+ if (mode.devServer?.command && trimmed === mode.devServer.command.trim()) {
119
+ return ALLOW;
120
+ }
121
+ // Default-deny with a curated allowlist: single unchained commands only.
122
+ if (!BASH_METACHARS.test(trimmed) && BASH_ALLOWLIST.some((re) => re.test(trimmed))) {
123
+ return ALLOW;
124
+ }
125
+ return deny(`That command isn't on ${modeName} mode's safe list, so it wasn't run.`, `bash is default-deny in mode "${modeName}" (shell writes would bypass writeScopes) — use the file tools for edits inside writeScopes, the configured devServer command for previews, or offer a handoff`);
126
+ }
127
+ export function decideGuard(input, resolved) {
128
+ // Layer-2 invisibility: no active mode, no opinions.
129
+ if (!resolved)
130
+ return ALLOW;
131
+ if (typeof input !== "object" || input === null)
132
+ return failClosed(resolved.modeName);
133
+ const hook = input;
134
+ if (typeof hook.tool_name !== "string")
135
+ return failClosed(resolved.modeName);
136
+ const toolInput = typeof hook.tool_input === "object" && hook.tool_input !== null
137
+ ? hook.tool_input
138
+ : {};
139
+ if (FILE_TOOLS.has(hook.tool_name)) {
140
+ const rawPath = toolInput["file_path"] ?? toolInput["notebook_path"];
141
+ if (typeof rawPath !== "string" || rawPath.length === 0) {
142
+ return failClosed(resolved.modeName);
143
+ }
144
+ const cwd = typeof hook.cwd === "string" && hook.cwd.length > 0 ? hook.cwd : process.cwd();
145
+ const rel = relativize(rawPath, cwd);
146
+ if (rel === null) {
147
+ return deny(`That file is outside this project, so ${resolved.modeName} mode left it alone.`, `path resolves outside the repo root — always denied in operator modes`);
148
+ }
149
+ return checkPath(rel, resolved);
150
+ }
151
+ if (hook.tool_name === "Bash") {
152
+ const command = toolInput["command"];
153
+ if (typeof command !== "string")
154
+ return failClosed(resolved.modeName);
155
+ return checkBash(command, resolved);
156
+ }
157
+ // Read-only / non-mutating tools pass through untouched.
158
+ return ALLOW;
159
+ }
160
+ //# sourceMappingURL=guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.js","sourceRoot":"","sources":["../../src/mode/guard.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/mode/init.ts"],"names":[],"mappings":"AAgBA,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,gCAAgC,CAAC;AAExC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAkCD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB5D;AAkBD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAcpF;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,CA6C3E"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/mode/init.ts"],"names":[],"mappings":"AAgBA,OAAO,EAGL,KAAK,UAAU,EAChB,MAAM,gCAAgC,CAAC;AAIxC,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAkCD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CAiB5D;AAkBD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,UAAU,CAcpF;AAED,wBAAgB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,cAAc,CAwD3E"}
package/dist/mode/init.js CHANGED
@@ -12,7 +12,8 @@
12
12
  import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
15
- import { parseDarkFactoryConfig, } from "@momentiq/dark-factory-schemas";
15
+ import { MODE_NAME_RE, parseDarkFactoryConfig, } from "@momentiq/dark-factory-schemas";
16
+ import { loadModeConfig } from "./loadConfig.js";
16
17
  const DEFAULT_PROTECTED_PATHS = [
17
18
  "**/auth/**",
18
19
  "**/api/**",
@@ -99,10 +100,18 @@ export function buildDefaultModeEntry(repoRoot, modeName) {
99
100
  return entry;
100
101
  }
101
102
  export function initMode(repoRoot, modeName) {
102
- const configPath = join(repoRoot, "darkfactory.yaml");
103
- const existingText = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
104
- const existingRaw = existingText.trim().length > 0 ? parseYaml(existingText) : {};
105
- const existingConfig = parseDarkFactoryConfig(existingRaw ?? {});
103
+ if (!MODE_NAME_RE.test(modeName)) {
104
+ // Validated BEFORE any file work: the name is interpolated into
105
+ // generated YAML (comment lines + map key) newlines or comment
106
+ // breakouts are injection, not typos. Same rule the canonical parser
107
+ // enforces, applied early for a better error.
108
+ throw new Error(`df mode init: invalid mode name ${JSON.stringify(modeName)} — mode names must be ` +
109
+ `lowercase slugs matching ${MODE_NAME_RE.source} (e.g. "designer").`);
110
+ }
111
+ // Shared read → parseYaml → parseDarkFactoryConfig ritual (with the raw
112
+ // `text` init appends to) lives in loadModeConfig — one interpretation of
113
+ // darkfactory.yaml across the `df mode …` surface.
114
+ const { configPath, text: existingText, config: existingConfig } = loadModeConfig(repoRoot);
106
115
  const entry = buildDefaultModeEntry(repoRoot, modeName);
107
116
  if (existingConfig.modes?.[modeName]) {
108
117
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/mode/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/mode/init.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8CAA8C;AAC9C,EAAE;AACF,sBAAsB;AACtB,8EAA8E;AAC9E,mEAAmE;AACnE,4EAA4E;AAC5E,+EAA+E;AAC/E,8EAA8E;AAC9E,yEAAyE;AACzE,6EAA6E;AAE7E,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC;AACtE,OAAO,EACL,YAAY,EACZ,sBAAsB,GAEvB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AASjD,MAAM,uBAAuB,GAAG;IAC9B,YAAY;IACZ,WAAW;IACX,kBAAkB;IAClB,YAAY;IACZ,eAAe;IACf,cAAc;IACd,mBAAmB;CACpB,CAAC;AAEF,MAAM,yBAAyB,GAAG;IAChC,gBAAgB;IAChB,SAAS;IACT,WAAW;IACX,YAAY;IACZ,KAAK;IACL,YAAY;IACZ,OAAO;IACP,QAAQ;IACR,QAAQ;CACT,CAAC;AAEF,MAAM,2BAA2B,GAAG,CAAC,gBAAgB,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC;AAEzF,SAAS,KAAK,CAAC,IAAY;IACzB,IAAI,CAAC;QACH,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,SAAS,IAAI,yBAAyB,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,SAAS,KAAK,CAAC,CAAC;IACvE,CAAC;IACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,IAAI,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;QACpB,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YACjD,KAAK,MAAM,GAAG,IAAI,2BAA2B,EAAE,CAAC;gBAC9C,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;oBACtC,MAAM,CAAC,IAAI,CAAC,QAAQ,KAAK,IAAI,GAAG,KAAK,CAAC,CAAC;gBACzC,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACxB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB;IACvC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;IAC/C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAEnD,CAAC;QACF,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,uBAAuB,EAAE,CAAC;QAClE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,wEAAwE;IAC1E,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,QAAgB,EAAE,QAAgB;IACtE,MAAM,WAAW,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC5C,MAAM,KAAK,GAAe;QACxB,WAAW;QACX,cAAc,EAAE,CAAC,GAAG,uBAAuB,CAAC;QAC5C,gBAAgB,EAAE,MAAM;QACxB,UAAU,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,GAAG,EAAE;QAC3C,YAAY,EAAE,EAAE,QAAQ,EAAE,WAAW,EAAE;QACvC,QAAQ,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE;KACjC,CAAC;IACF,IAAI,SAAS;QAAE,KAAK,CAAC,SAAS,GAAG,SAAS,CAAC;IAC3C,KAAK,QAAQ,CAAC,CAAC,sDAAsD;IACrE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,QAAgB,EAAE,QAAgB;IACzD,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,gEAAgE;QAChE,iEAAiE;QACjE,qEAAqE;QACrE,8CAA8C;QAC9C,MAAM,IAAI,KAAK,CACb,mCAAmC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,wBAAwB;YACjF,4BAA4B,YAAY,CAAC,MAAM,qBAAqB,CACvE,CAAC;IACJ,CAAC;IACD,wEAAwE;IACxE,0EAA0E;IAC1E,mDAAmD;IACnD,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,cAAc,EAAE,GAC9D,cAAc,CAAC,QAAQ,CAAC,CAAC;IAE3B,MAAM,KAAK,GAAG,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAExD,IAAI,cAAc,CAAC,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrC,OAAO;YACL,MAAM,EAAE,QAAQ;YAChB,QAAQ;YACR,UAAU;YACV,WAAW,EAAE,cAAc,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,WAAW,IAAI,EAAE;SAC/D,CAAC;IACJ,CAAC;IACD,IAAI,cAAc,CAAC,KAAK,EAAE,CAAC;QACzB,wEAAwE;QACxE,sEAAsE;QACtE,0EAA0E;QAC1E,MAAM,IAAI,KAAK,CACb,iEAAiE;YAC/D,aAAa,QAAQ,kDAAkD;YACvE,+CAA+C,CAClD,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GACT,mEAAmE,QAAQ,OAAO;QAClF,8EAA8E;QAC9E,aAAa,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAElD,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACvF,MAAM,SAAS,GAAG,YAAY,GAAG,SAAS,GAAG,KAAK,CAAC;IAEnD,sEAAsE;IACtE,sBAAsB,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC;IAE7C,aAAa,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IACrC,OAAO;QACL,MAAM,EAAE,SAAS;QACjB,QAAQ;QACR,UAAU;QACV,WAAW,EAAE,KAAK,CAAC,WAAW,IAAI,EAAE;KACrC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { type DarkFactoryConfig } from "@momentiq/dark-factory-schemas";
2
+ export declare const MODE_CONFIG_FILENAME = "darkfactory.yaml";
3
+ export interface LoadedModeConfig {
4
+ /**
5
+ * Absolute path to `darkfactory.yaml`, whether or not the file exists
6
+ * (`resolve`d against `repoRoot`, matching the skills-side loader).
7
+ */
8
+ readonly configPath: string;
9
+ /**
10
+ * Raw file text. `""` when the file is missing OR exists but is empty — so
11
+ * `text === ""` alone does NOT distinguish those two; a caller that needs
12
+ * the distinction checks the path on disk. `df mode init` appends to this
13
+ * verbatim so hand-maintained comments/formatting survive.
14
+ */
15
+ readonly text: string;
16
+ /**
17
+ * Canonically-parsed config. `{}` (defaults everywhere) when the file is
18
+ * missing, empty, or comment-only. Throws when the file exists with content
19
+ * that `parseDarkFactoryConfig` rejects — a mode command must fail closed on
20
+ * a malformed config rather than operate on a silently-defaulted one.
21
+ */
22
+ readonly config: DarkFactoryConfig;
23
+ }
24
+ /**
25
+ * Load `darkfactory.yaml` from `repoRoot` for the `df mode …` commands.
26
+ *
27
+ * A missing file, an empty file, and a comment/whitespace-only file all
28
+ * resolve to the empty config (`{}`) — the same defaulted-everywhere case the
29
+ * canonical parser treats `parseDarkFactoryConfig({})` as. Any other content
30
+ * is YAML-parsed then handed to `parseDarkFactoryConfig`, which throws a
31
+ * `SchemaError` naming the offending path on an invalid shape.
32
+ */
33
+ export declare function loadModeConfig(repoRoot: string): LoadedModeConfig;
34
+ //# sourceMappingURL=loadConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadConfig.d.ts","sourceRoot":"","sources":["../../src/mode/loadConfig.ts"],"names":[],"mappings":"AAmBA,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,gCAAgC,CAAC;AAExC,eAAO,MAAM,oBAAoB,qBAAqB,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACpC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAQjE"}
@@ -0,0 +1,38 @@
1
+ // Cycle 27 — shared `darkfactory.yaml` loader for the `df mode …` surface.
2
+ //
3
+ // `df mode status` and `df mode init` both performed the same
4
+ // read → parseYaml → parseDarkFactoryConfig ritual inline. This module is the
5
+ // single home for that ritual so there is ONE place that decides how the mode
6
+ // commands turn the file on disk into a typed config.
7
+ //
8
+ // It delegates shape validation to the canonical `parseDarkFactoryConfig`
9
+ // (@momentiq/dark-factory-schemas) — the exact same parser `skills/config.ts`
10
+ // (via `loadDarkFactoryConfig`) and `df doctor` (`checkDarkFactoryConfig`) use.
11
+ // There is no second interpretation of `darkfactory.yaml`; the two loaders
12
+ // differ only in ergonomics (this one returns the raw `text` so `df mode init`
13
+ // can APPEND to the consumer's file without re-serializing it; the skills
14
+ // loader adds an `isDefault` flag + a friendly error wrapper).
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { resolve } from "node:path";
17
+ import { parse as parseYaml } from "yaml";
18
+ import { parseDarkFactoryConfig, } from "@momentiq/dark-factory-schemas";
19
+ export const MODE_CONFIG_FILENAME = "darkfactory.yaml";
20
+ /**
21
+ * Load `darkfactory.yaml` from `repoRoot` for the `df mode …` commands.
22
+ *
23
+ * A missing file, an empty file, and a comment/whitespace-only file all
24
+ * resolve to the empty config (`{}`) — the same defaulted-everywhere case the
25
+ * canonical parser treats `parseDarkFactoryConfig({})` as. Any other content
26
+ * is YAML-parsed then handed to `parseDarkFactoryConfig`, which throws a
27
+ * `SchemaError` naming the offending path on an invalid shape.
28
+ */
29
+ export function loadModeConfig(repoRoot) {
30
+ const configPath = resolve(repoRoot, MODE_CONFIG_FILENAME);
31
+ const text = existsSync(configPath) ? readFileSync(configPath, "utf8") : "";
32
+ // Only YAML-parse when there is real content: the parser returns null for a
33
+ // comment/whitespace-only file, so `?? {}` folds that into the empty config.
34
+ const raw = text.trim().length > 0 ? parseYaml(text) : {};
35
+ const config = parseDarkFactoryConfig(raw ?? {});
36
+ return { configPath, text, config };
37
+ }
38
+ //# sourceMappingURL=loadConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadConfig.js","sourceRoot":"","sources":["../../src/mode/loadConfig.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,EAAE;AACF,8DAA8D;AAC9D,8EAA8E;AAC9E,8EAA8E;AAC9E,sDAAsD;AACtD,EAAE;AACF,0EAA0E;AAC1E,8EAA8E;AAC9E,gFAAgF;AAChF,2EAA2E;AAC3E,+EAA+E;AAC/E,0EAA0E;AAC1E,+DAA+D;AAE/D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EACL,sBAAsB,GAEvB,MAAM,gCAAgC,CAAC;AAExC,MAAM,CAAC,MAAM,oBAAoB,GAAG,kBAAkB,CAAC;AAwBvD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5E,4EAA4E;IAC5E,6EAA6E;IAC7E,MAAM,GAAG,GAAY,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IACnE,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;IACjD,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AACtC,CAAC"}
@@ -0,0 +1,47 @@
1
+ <!-- DF-PROFILE: baseline -->
2
+ # Designer Mode — session doctrine (baseline)
3
+
4
+ You are working with a **non-technical operator** — a designer, PM, or founder
5
+ describing product intent. You own every technical decision; they own every
6
+ product decision. Their trust in this session depends on the rules below.
7
+
8
+ ## Language
9
+
10
+ - Speak plain product language. Never show code, diffs, stack traces, file
11
+ trees, or git/terminal vocabulary unless the operator explicitly asks.
12
+ - Translate progress and problems into outcomes: what changed, what they'll
13
+ see, what happens next. Never paste raw errors — say what went wrong in one
14
+ sentence and what you're doing about it.
15
+
16
+ ## Questions
17
+
18
+ - Ask ONLY product or visual questions ("Should the banner stay visible while
19
+ scrolling?"), never technical ones (frameworks, files, state, APIs — decide
20
+ those yourself, following the repo's existing conventions).
21
+ - Prefer multiple choice with 2–3 concrete options plus "you decide". One
22
+ question at a time. If you can make a reasonable call, make it and say so.
23
+
24
+ ## Design doctrine
25
+
26
+ - `DESIGN.md` at the repo root is the design source of truth: take every
27
+ color, font, and spacing value from its tokens; follow its do's and don'ts.
28
+ - Reuse existing components before creating anything new — extend, don't fork.
29
+ - Never remove accessibility affordances (focus rings, labels, contrast).
30
+
31
+ ## Verification
32
+
33
+ - Show, don't tell: run the dev server and present before/after screenshots
34
+ for every visual change. Iterate until the operator says it looks right.
35
+
36
+ ## Guardrails
37
+
38
+ - When a tool call is denied by the mode guard, do NOT retry or work around
39
+ it. Explain in one plain sentence, then offer to package the request for an
40
+ engineer (a handoff) — framed as progress, not rejection.
41
+ - Never bypass, weaken, or ask the operator to bypass any gate.
42
+
43
+ ## Shipping
44
+
45
+ - Branches, PRs, reviews, and merges are invisible plumbing: run them
46
+ yourself and report status plainly ("It's in review with our automated
47
+ checks — I'll tell you when it's live.").
@@ -1,4 +1,4 @@
1
- import type { DarkFactoryConfig, ModeConfig } from "@momentiq/dark-factory-schemas";
1
+ import { type DarkFactoryConfig, type ModeConfig } from "@momentiq/dark-factory-schemas";
2
2
  /**
3
3
  * Pure precedence resolver. The CLI arg map can contain `mode` as a string
4
4
  * (the value), `true` (bare `--mode` with no value), or be absent. The env
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/mode/resolve.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAEpF;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CAAE,EAC7C,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACpC,MAAM,GAAG,SAAS,CAYpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,iBAAiB,EACzB,QAAQ,EAAE,MAAM,GACf,YAAY,CAiBd"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/mode/resolve.ts"],"names":[],"mappings":"AAcA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,UAAU,EAChB,MAAM,gCAAgC,CAAC;AAcxC;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CAAE,EAC7C,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACpC,MAAM,GAAG,SAAS,CAYpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,iBAAiB,EACzB,QAAQ,EAAE,MAAM,GACf,YAAY,CAyBd"}
@@ -11,6 +11,16 @@
11
11
  // 2. `DF_MODE` env var.
12
12
  // 3. No mode — unlike profiles there is NO default: an unmoded session is
13
13
  // the normal engineer path, so absence must stay meaningful.
14
+ import { MODE_NAME_RE, } from "@momentiq/dark-factory-schemas";
15
+ function assertSlug(value, source) {
16
+ if (!MODE_NAME_RE.test(value)) {
17
+ // JSON.stringify renders the hostile value safely escaped (newlines,
18
+ // backticks, quotes) — never raw, and never inside a copyable command.
19
+ throw new Error(`df mode: invalid mode name ${JSON.stringify(value)} from ${source} — ` +
20
+ `mode names are lowercase slugs matching ${MODE_NAME_RE.source}.`);
21
+ }
22
+ return value;
23
+ }
14
24
  /**
15
25
  * Pure precedence resolver. The CLI arg map can contain `mode` as a string
16
26
  * (the value), `true` (bare `--mode` with no value), or be absent. The env
@@ -22,20 +32,26 @@ export function resolveMode(args, env) {
22
32
  if (typeof flagRaw === "string") {
23
33
  const trimmed = flagRaw.trim();
24
34
  if (trimmed.length > 0)
25
- return trimmed;
35
+ return assertSlug(trimmed, "--mode");
26
36
  }
27
37
  const envRaw = env.DF_MODE;
28
38
  if (typeof envRaw === "string") {
29
39
  const trimmed = envRaw.trim();
30
40
  if (trimmed.length > 0)
31
- return trimmed;
41
+ return assertSlug(trimmed, "DF_MODE");
32
42
  }
33
43
  return undefined;
34
44
  }
35
45
  export function resolveModeWithConfig(config, modeName) {
46
+ // Callers reach here via resolveMode (slug-validated), but this function is
47
+ // exported — re-assert so no path interpolates an unvalidated name.
48
+ if (!MODE_NAME_RE.test(modeName)) {
49
+ throw new Error(`df mode: invalid mode name ${JSON.stringify(modeName)} — ` +
50
+ `mode names are lowercase slugs matching ${MODE_NAME_RE.source}.`);
51
+ }
36
52
  if (!config.modes) {
37
53
  throw new Error(`df mode: no \`modes\` catalog in darkfactory.yaml — mode "${modeName}" ` +
38
- `cannot be resolved. Run \`df mode init ${modeName}\` to seed one.`);
54
+ `cannot be resolved. Run \`df mode init <name>\` (e.g. \`df mode init designer\`) to seed one.`);
39
55
  }
40
56
  const mode = config.modes[modeName];
41
57
  if (!mode) {
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/mode/resolve.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,EAAE;AACF,2CAA2C;AAC3C,iCAAiC;AACjC,0BAA0B;AAC1B,4EAA4E;AAC5E,kEAAkE;AAIlE;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,IAA6C,EAC7C,GAAqC;IAErC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC;IAC1B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC;IACzC,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC;IAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,OAAO,CAAC;IACzC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAaD,MAAM,UAAU,qBAAqB,CACnC,MAAyB,EACzB,QAAgB;IAEhB,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,6DAA6D,QAAQ,IAAI;YACvE,0CAA0C,QAAQ,iBAAiB,CACtE,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,IAAI,KAAK,CACb,0BAA0B,QAAQ,KAAK;YACrC,oBAAoB,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI;YAC9E,kEAAkE,CACrE,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../src/mode/resolve.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,EAAE;AACF,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,EAAE;AACF,2CAA2C;AAC3C,iCAAiC;AACjC,0BAA0B;AAC1B,4EAA4E;AAC5E,kEAAkE;AAElE,OAAO,EACL,YAAY,GAGb,MAAM,gCAAgC,CAAC;AAExC,SAAS,UAAU,CAAC,KAAa,EAAE,MAA4B;IAC7D,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,qEAAqE;QACrE,uEAAuE;QACvE,MAAM,IAAI,KAAK,CACb,8BAA8B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,SAAS,MAAM,KAAK;YACrE,2CAA2C,YAAY,CAAC,MAAM,GAAG,CACpE,CAAC;IACJ,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CACzB,IAA6C,EAC7C,GAAqC;IAErC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC;IAC1B,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC/B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC/D,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC;IAC3B,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC9B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAaD,MAAM,UAAU,qBAAqB,CACnC,MAAyB,EACzB,QAAgB;IAEhB,4EAA4E;IAC5E,oEAAoE;IACpE,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CACb,8BAA8B,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK;YACzD,2CAA2C,YAAY,CAAC,MAAM,GAAG,CACpE,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,6DAA6D,QAAQ,IAAI;YACvE,+FAA+F,CAClG,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,IAAI,KAAK,CACb,0BAA0B,QAAQ,KAAK;YACrC,oBAAoB,SAAS,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI;YAC9E,kEAAkE,CACrE,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { ModeConfig } from "@momentiq/dark-factory-schemas";
2
+ /**
3
+ * Bounded glob matcher for mode-catalog patterns. Supports exactly the
4
+ * forms the catalog uses — `**` (any depth, including none, across
5
+ * separators), `*` (any run within a segment), literal text — via safe
6
+ * regex translation. Deliberately NOT a general glob engine and NOT a new
7
+ * dependency: catalog patterns are repo-author-controlled.
8
+ */
9
+ export declare function globMatch(pattern: string, path: string): boolean;
10
+ export declare const DEPENDENCY_MANIFESTS: readonly string[];
11
+ /**
12
+ * The rule that fired for a single repo-relative path against a mode
13
+ * catalog. `allow` means the path is within the mode's permitted surface;
14
+ * every other kind is a scope violation, in precedence order.
15
+ */
16
+ export type ModePathClassification = {
17
+ kind: "allow";
18
+ } | {
19
+ kind: "protected";
20
+ } | {
21
+ kind: "dependency";
22
+ } | {
23
+ kind: "out-of-scope";
24
+ };
25
+ /**
26
+ * Classify ONE repo-relative, forward-slash path against a mode catalog.
27
+ * Pure and total. Precedence (highest → lowest), matching spec §4.3 /
28
+ * §4.4:
29
+ * 1. `protectedPaths` — ALWAYS wins over writeScopes on overlap.
30
+ * 2. dependency manifest under `dependencyPolicy: "none"`.
31
+ * 3. `writeScopes` membership — only enforced when the mode declares at
32
+ * least one write scope (an empty/absent writeScopes list means "no
33
+ * scope narrowing", so nothing is out-of-scope on that axis).
34
+ *
35
+ * The caller supplies an already-relativized path (no leading `/`, no
36
+ * `..`); path canonicalization / repo-escape rejection is the caller's
37
+ * concern (the guard relativizes tool-call paths; the critic reads
38
+ * git-relative diff paths, which are already repo-relative).
39
+ */
40
+ export declare function classifyModePath(path: string, mode: ModeConfig): ModePathClassification;
41
+ //# sourceMappingURL=scope-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-policy.d.ts","sourceRoot":"","sources":["../../src/mode/scope-policy.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAEjE;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CA4BhE;AAMD,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAOjD,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAC9B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,GACjB;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,GACtB;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,CAAC;AAE7B;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,sBAAsB,CAcvF"}
@@ -0,0 +1,97 @@
1
+ // Cycle 27 Phase C — shared operator-mode scope-policy core.
2
+ //
3
+ // Single source of truth for the catalog scope decision that BOTH
4
+ // enforcement layers consume (spec §4.4 frames them as ONE policy, so
5
+ // they must not drift):
6
+ // - Layer 2 (`mode/guard.ts` PreToolUse hook) — per-tool-call, live in
7
+ // the session, dual-register deny messages.
8
+ // - Layer 4 (`adapters/mode-scope.ts` CI/pre-push critic) — per-diff,
9
+ // tamper-proof, `blocker` findings.
10
+ //
11
+ // The precedence is invariant across both: `protectedPaths` ALWAYS wins
12
+ // over `writeScopes` on overlap, then dependency-manifest edits under
13
+ // `dependencyPolicy: "none"`, then writeScopes membership. Extracting the
14
+ // classifier here means a change to that precedence lands in one place and
15
+ // is enforced identically at both layers.
16
+ /**
17
+ * Bounded glob matcher for mode-catalog patterns. Supports exactly the
18
+ * forms the catalog uses — `**` (any depth, including none, across
19
+ * separators), `*` (any run within a segment), literal text — via safe
20
+ * regex translation. Deliberately NOT a general glob engine and NOT a new
21
+ * dependency: catalog patterns are repo-author-controlled.
22
+ */
23
+ export function globMatch(pattern, path) {
24
+ let re = "";
25
+ for (let i = 0; i < pattern.length; i++) {
26
+ const ch = pattern[i];
27
+ if (ch === "*") {
28
+ if (pattern[i + 1] === "*") {
29
+ // `**` = any depth. Two shapes: a `**/` prefix/interior swallows its
30
+ // slash (`(?:.*/)?`) so `**/x` also matches a root-level `x`; a trailing
31
+ // `**` becomes `.*`, so `a/**` → `^a/.*$` matches everything UNDER `a/`
32
+ // at any depth (the paths classified here are files, never a bare dir).
33
+ const next = pattern[i + 2];
34
+ if (next === "/") {
35
+ re += "(?:.*/)?";
36
+ i += 2;
37
+ }
38
+ else {
39
+ re += ".*";
40
+ i += 1;
41
+ }
42
+ }
43
+ else {
44
+ re += "[^/]*";
45
+ }
46
+ }
47
+ else if ("\\^$.|?+()[]{}".includes(ch)) {
48
+ re += `\\${ch}`;
49
+ }
50
+ else {
51
+ re += ch;
52
+ }
53
+ }
54
+ return new RegExp(`^${re}$`).test(path);
55
+ }
56
+ // Manifest/lockfile basenames that a `dependencyPolicy: "none"` mode may
57
+ // not touch. Basename match (a nested `apps/web/package.json` is still a
58
+ // dependency manifest). Kept in lockstep with the guard's Bash dependency
59
+ // denylist by construction — both live in this module's consumers.
60
+ export const DEPENDENCY_MANIFESTS = [
61
+ "package.json",
62
+ "package-lock.json",
63
+ "npm-shrinkwrap.json",
64
+ "pnpm-lock.yaml",
65
+ "yarn.lock",
66
+ "bun.lockb",
67
+ ];
68
+ /**
69
+ * Classify ONE repo-relative, forward-slash path against a mode catalog.
70
+ * Pure and total. Precedence (highest → lowest), matching spec §4.3 /
71
+ * §4.4:
72
+ * 1. `protectedPaths` — ALWAYS wins over writeScopes on overlap.
73
+ * 2. dependency manifest under `dependencyPolicy: "none"`.
74
+ * 3. `writeScopes` membership — only enforced when the mode declares at
75
+ * least one write scope (an empty/absent writeScopes list means "no
76
+ * scope narrowing", so nothing is out-of-scope on that axis).
77
+ *
78
+ * The caller supplies an already-relativized path (no leading `/`, no
79
+ * `..`); path canonicalization / repo-escape rejection is the caller's
80
+ * concern (the guard relativizes tool-call paths; the critic reads
81
+ * git-relative diff paths, which are already repo-relative).
82
+ */
83
+ export function classifyModePath(path, mode) {
84
+ const basename = path.split("/").pop() ?? path;
85
+ if ((mode.protectedPaths ?? []).some((p) => globMatch(p, path))) {
86
+ return { kind: "protected" };
87
+ }
88
+ if (mode.dependencyPolicy === "none" && DEPENDENCY_MANIFESTS.includes(basename)) {
89
+ return { kind: "dependency" };
90
+ }
91
+ const scopes = mode.writeScopes ?? [];
92
+ if (scopes.length > 0 && !scopes.some((p) => globMatch(p, path))) {
93
+ return { kind: "out-of-scope" };
94
+ }
95
+ return { kind: "allow" };
96
+ }
97
+ //# sourceMappingURL=scope-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-policy.js","sourceRoot":"","sources":["../../src/mode/scope-policy.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/mode/status.ts"],"names":[],"mappings":"AAOA,OAAO,EAEL,KAAK,UAAU,EAChB,MAAM,gCAAgC,CAAC;AAIxC,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzD,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CAAE,EAC7C,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACpC,gBAAgB,CAWlB"}
1
+ {"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/mode/status.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAKjE,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,KAAK,CAAA;CAAE,GACjB;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzD,wBAAgB,UAAU,CACxB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,CAAA;CAAE,EAC7C,GAAG,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,GACpC,gBAAgB,CAOlB"}
@@ -1,19 +1,12 @@
1
1
  // Cycle 27 Phase A — `df mode status`: report the session's active operator
2
2
  // mode. "No mode" is the normal engineer path, not an error.
3
- import { existsSync, readFileSync } from "node:fs";
4
- import { join } from "node:path";
5
- import { parse as parseYaml } from "yaml";
6
- import { parseDarkFactoryConfig, } from "@momentiq/dark-factory-schemas";
3
+ import { loadModeConfig } from "./loadConfig.js";
7
4
  import { resolveMode, resolveModeWithConfig } from "./resolve.js";
8
5
  export function modeStatus(repoRoot, args, env) {
9
6
  const modeName = resolveMode(args, env);
10
7
  if (!modeName)
11
8
  return { active: false };
12
- const configPath = join(repoRoot, "darkfactory.yaml");
13
- const raw = existsSync(configPath)
14
- ? (parseYaml(readFileSync(configPath, "utf8")) ?? {})
15
- : {};
16
- const config = parseDarkFactoryConfig(raw);
9
+ const { config } = loadModeConfig(repoRoot);
17
10
  const resolved = resolveModeWithConfig(config, modeName);
18
11
  return { active: true, modeName: resolved.modeName, mode: resolved.mode };
19
12
  }
@@ -1 +1 @@
1
- {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/mode/status.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,6DAA6D;AAE7D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EACL,sBAAsB,GAEvB,MAAM,gCAAgC,CAAC;AAExC,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAMlE,MAAM,UAAU,UAAU,CACxB,QAAgB,EAChB,IAA6C,EAC7C,GAAqC;IAErC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAExC,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,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC5E,CAAC"}
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/mode/status.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,6DAA6D;AAI7D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAMlE,MAAM,UAAU,UAAU,CACxB,QAAgB,EAChB,IAA6C,EAC7C,GAAqC;IAErC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IACxC,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAExC,MAAM,EAAE,MAAM,EAAE,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC5C,MAAM,QAAQ,GAAG,qBAAqB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzD,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;AAC5E,CAAC"}