trackops 2.0.4 → 2.0.5

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 (90) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +695 -640
  3. package/bin/trackops.js +116 -116
  4. package/lib/config.js +326 -326
  5. package/lib/control.js +208 -208
  6. package/lib/env.js +244 -244
  7. package/lib/init.js +325 -325
  8. package/lib/locale.js +41 -41
  9. package/lib/opera-bootstrap.js +942 -936
  10. package/lib/opera.js +495 -486
  11. package/lib/preferences.js +74 -74
  12. package/lib/registry.js +214 -214
  13. package/lib/release.js +56 -56
  14. package/lib/runtime-state.js +144 -144
  15. package/lib/skills.js +74 -57
  16. package/lib/workspace.js +260 -260
  17. package/locales/en.json +192 -170
  18. package/locales/es.json +192 -170
  19. package/package.json +61 -58
  20. package/scripts/postinstall-locale.js +21 -21
  21. package/scripts/skills-marketplace-smoke.js +124 -124
  22. package/scripts/smoke-tests.js +558 -554
  23. package/scripts/sync-skill-version.js +21 -21
  24. package/scripts/validate-skill.js +103 -103
  25. package/skills/trackops/SKILL.md +126 -122
  26. package/skills/trackops/agents/openai.yaml +7 -7
  27. package/skills/trackops/locales/en/SKILL.md +126 -122
  28. package/skills/trackops/locales/en/references/activation.md +94 -90
  29. package/skills/trackops/locales/en/references/troubleshooting.md +73 -67
  30. package/skills/trackops/locales/en/references/workflow.md +55 -32
  31. package/skills/trackops/references/activation.md +94 -90
  32. package/skills/trackops/references/troubleshooting.md +73 -67
  33. package/skills/trackops/references/workflow.md +55 -32
  34. package/skills/trackops/skill.json +29 -29
  35. package/templates/hooks/post-checkout +2 -2
  36. package/templates/hooks/post-commit +2 -2
  37. package/templates/hooks/post-merge +2 -2
  38. package/templates/opera/agent.md +28 -27
  39. package/templates/opera/architecture/dependency-graph.md +24 -24
  40. package/templates/opera/architecture/runtime-automation.md +24 -24
  41. package/templates/opera/architecture/runtime-operations.md +34 -34
  42. package/templates/opera/en/agent.md +22 -21
  43. package/templates/opera/en/architecture/dependency-graph.md +24 -24
  44. package/templates/opera/en/architecture/runtime-automation.md +24 -24
  45. package/templates/opera/en/architecture/runtime-operations.md +34 -34
  46. package/templates/opera/en/reviews/delivery-audit.md +18 -18
  47. package/templates/opera/en/reviews/integration-audit.md +18 -18
  48. package/templates/opera/en/router.md +24 -19
  49. package/templates/opera/references/autonomy-and-recovery.md +117 -117
  50. package/templates/opera/references/opera-cycle.md +193 -193
  51. package/templates/opera/registry.md +28 -28
  52. package/templates/opera/reviews/delivery-audit.md +18 -18
  53. package/templates/opera/reviews/integration-audit.md +18 -18
  54. package/templates/opera/router.md +54 -49
  55. package/templates/skills/changelog-updater/SKILL.md +69 -69
  56. package/templates/skills/commiter/SKILL.md +99 -99
  57. package/templates/skills/opera-contract-auditor/SKILL.md +38 -38
  58. package/templates/skills/opera-contract-auditor/locales/en/SKILL.md +38 -38
  59. package/templates/skills/opera-policy-guard/SKILL.md +26 -26
  60. package/templates/skills/opera-policy-guard/locales/en/SKILL.md +26 -26
  61. package/templates/skills/opera-skill/SKILL.md +279 -0
  62. package/templates/skills/opera-skill/locales/en/SKILL.md +279 -0
  63. package/templates/skills/opera-skill/locales/en/references/phase-dod.md +138 -0
  64. package/templates/skills/opera-skill/references/phase-dod.md +138 -0
  65. package/templates/skills/project-starter-skill/SKILL.md +150 -131
  66. package/templates/skills/project-starter-skill/locales/en/SKILL.md +143 -105
  67. package/templates/skills/project-starter-skill/references/opera-cycle.md +195 -193
  68. package/ui/css/base.css +284 -284
  69. package/ui/css/charts.css +425 -425
  70. package/ui/css/components.css +1107 -1107
  71. package/ui/css/onboarding.css +133 -133
  72. package/ui/css/terminal.css +125 -125
  73. package/ui/css/timeline.css +58 -58
  74. package/ui/css/tokens.css +284 -284
  75. package/ui/favicon.svg +5 -5
  76. package/ui/index.html +99 -99
  77. package/ui/js/charts.js +526 -526
  78. package/ui/js/console-logger.js +172 -172
  79. package/ui/js/filters.js +247 -247
  80. package/ui/js/icons.js +129 -129
  81. package/ui/js/keyboard.js +229 -229
  82. package/ui/js/router.js +142 -142
  83. package/ui/js/theme.js +100 -100
  84. package/ui/js/time-tracker.js +248 -248
  85. package/ui/js/views/dashboard.js +870 -870
  86. package/ui/js/views/flash.js +47 -47
  87. package/ui/js/views/projects.js +745 -745
  88. package/ui/js/views/scrum.js +476 -476
  89. package/ui/js/views/settings.js +331 -331
  90. package/ui/js/views/timeline.js +265 -265
@@ -12,7 +12,7 @@ const { resolveLocalizedFile } = require("./resources");
12
12
 
13
13
  const TEMPLATES_DIR = path.join(__dirname, "..", "templates", "opera");
14
14
 
15
- const SERVICE_HINTS = [
15
+ const SERVICE_HINTS = [
16
16
  ["openai", "OpenAI"],
17
17
  ["anthropic", "Anthropic"],
18
18
  ["stripe", "Stripe"],
@@ -24,60 +24,60 @@ const SERVICE_HINTS = [
24
24
  ["s3", "Amazon S3"],
25
25
  ["aws", "AWS"],
26
26
  ["gcp", "Google Cloud"],
27
- ["azure", "Azure"],
28
- ];
29
-
30
- const TECHNICAL_LEVELS = ["low", "medium", "high", "senior"];
31
- const PROJECT_STATES = ["idea", "draft", "existing_repo", "advanced"];
32
- const DOC_STATES = ["none", "notes", "sos", "spec_dossier", "repo_docs"];
33
- const DECISION_OWNERSHIPS = ["user", "shared", "agent"];
34
- const BOOTSTRAP_MODES = ["auto", "direct", "handoff"];
35
- const QUALITY_STATUSES = ["ready", "needs_review", "blocked"];
36
- const CONTRACT_READINESS = ["hypothesis", "provisional", "verified", "locked"];
37
- const CONTRACT_VERSION = 3;
38
- const ENUM_ALIASES = {
39
- low: "low",
40
- bajo: "low",
41
- basic: "low",
42
- beginner: "low",
43
- medium: "medium",
44
- medio: "medium",
45
- mid: "medium",
46
- high: "high",
47
- alto: "high",
48
- advanced: "high",
49
- senior: "senior",
50
- idea: "idea",
51
- ideacion: "idea",
52
- ideation: "idea",
53
- draft: "draft",
54
- borrador: "draft",
55
- existing_repo: "existing_repo",
56
- existingrepo: "existing_repo",
57
- existing: "existing_repo",
58
- repo_existente: "existing_repo",
59
- "repo existente": "existing_repo",
60
- advanced_project: "advanced",
61
- avanzado: "advanced",
62
- advancedrepo: "advanced",
63
- none: "none",
64
- ninguna: "none",
65
- notes: "notes",
66
- notas: "notes",
67
- sos: "sos",
68
- spec_dossier: "spec_dossier",
69
- spec: "spec_dossier",
70
- dossier: "spec_dossier",
71
- repo_docs: "repo_docs",
72
- repodocs: "repo_docs",
73
- docs_repo: "repo_docs",
74
- user: "user",
75
- usuario: "user",
76
- shared: "shared",
77
- compartido: "shared",
78
- agent: "agent",
79
- agente: "agent",
80
- };
27
+ ["azure", "Azure"],
28
+ ];
29
+
30
+ const TECHNICAL_LEVELS = ["low", "medium", "high", "senior"];
31
+ const PROJECT_STATES = ["idea", "draft", "existing_repo", "advanced"];
32
+ const DOC_STATES = ["none", "notes", "sos", "spec_dossier", "repo_docs"];
33
+ const DECISION_OWNERSHIPS = ["user", "shared", "agent"];
34
+ const BOOTSTRAP_MODES = ["auto", "direct", "handoff"];
35
+ const QUALITY_STATUSES = ["ready", "needs_review", "blocked"];
36
+ const CONTRACT_READINESS = ["hypothesis", "provisional", "verified", "locked"];
37
+ const CONTRACT_VERSION = 3;
38
+ const ENUM_ALIASES = {
39
+ low: "low",
40
+ bajo: "low",
41
+ basic: "low",
42
+ beginner: "low",
43
+ medium: "medium",
44
+ medio: "medium",
45
+ mid: "medium",
46
+ high: "high",
47
+ alto: "high",
48
+ advanced: "high",
49
+ senior: "senior",
50
+ idea: "idea",
51
+ ideacion: "idea",
52
+ ideation: "idea",
53
+ draft: "draft",
54
+ borrador: "draft",
55
+ existing_repo: "existing_repo",
56
+ existingrepo: "existing_repo",
57
+ existing: "existing_repo",
58
+ repo_existente: "existing_repo",
59
+ "repo existente": "existing_repo",
60
+ advanced_project: "advanced",
61
+ avanzado: "advanced",
62
+ advancedrepo: "advanced",
63
+ none: "none",
64
+ ninguna: "none",
65
+ notes: "notes",
66
+ notas: "notes",
67
+ sos: "sos",
68
+ spec_dossier: "spec_dossier",
69
+ spec: "spec_dossier",
70
+ dossier: "spec_dossier",
71
+ repo_docs: "repo_docs",
72
+ repodocs: "repo_docs",
73
+ docs_repo: "repo_docs",
74
+ user: "user",
75
+ usuario: "user",
76
+ shared: "shared",
77
+ compartido: "shared",
78
+ agent: "agent",
79
+ agente: "agent",
80
+ };
81
81
 
82
82
  function nowIso() {
83
83
  return new Date().toISOString();
@@ -89,27 +89,27 @@ function git(args, root) {
89
89
  return result.stdout.trim();
90
90
  }
91
91
 
92
- function readText(filePath) {
93
- return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
94
- }
95
-
96
- function readJson(filePath) {
97
- if (!fs.existsSync(filePath)) return null;
98
- try {
99
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
100
- } catch (_error) {
101
- return null;
102
- }
103
- }
104
-
105
- function writeText(filePath, content) {
106
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
107
- fs.writeFileSync(filePath, content.replace(/\r?\n/g, "\n"), "utf8");
108
- }
109
-
110
- function writeJson(filePath, data) {
111
- writeText(filePath, `${JSON.stringify(data, null, 2)}\n`);
112
- }
92
+ function readText(filePath) {
93
+ return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
94
+ }
95
+
96
+ function readJson(filePath) {
97
+ if (!fs.existsSync(filePath)) return null;
98
+ try {
99
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
100
+ } catch (_error) {
101
+ return null;
102
+ }
103
+ }
104
+
105
+ function writeText(filePath, content) {
106
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
107
+ fs.writeFileSync(filePath, content.replace(/\r?\n/g, "\n"), "utf8");
108
+ }
109
+
110
+ function writeJson(filePath, data) {
111
+ writeText(filePath, `${JSON.stringify(data, null, 2)}\n`);
112
+ }
113
113
 
114
114
  function safeJson(text) {
115
115
  try {
@@ -119,75 +119,75 @@ function safeJson(text) {
119
119
  }
120
120
  }
121
121
 
122
- function unique(items) {
123
- return [...new Set((items || []).map((item) => String(item || "").trim()).filter(Boolean))];
124
- }
125
-
126
- function normalizeEnum(value, allowed) {
127
- const normalized = String(value || "").trim().toLowerCase().replace(/\s+/g, "_");
128
- const alias = ENUM_ALIASES[normalized] || normalized;
129
- return allowed.includes(alias) ? alias : null;
130
- }
131
-
132
- function choiceHint(allowed) {
133
- if (allowed === TECHNICAL_LEVELS) return "low|medium|high|senior (o bajo|medio|alto)";
134
- if (allowed === PROJECT_STATES) return "idea|draft|existing_repo|advanced";
135
- if (allowed === DOC_STATES) return "none|notes|sos|spec_dossier|repo_docs";
136
- if (allowed === DECISION_OWNERSHIPS) return "user|shared|agent (o usuario|compartido|agente)";
137
- return Array.isArray(allowed) ? allowed.join("|") : null;
138
- }
139
-
140
- function explanationModeFor(technicalLevel) {
141
- if (technicalLevel === "low") return "guided";
142
- if (technicalLevel === "medium") return "balanced";
143
- return "expert";
144
- }
145
-
146
- function bootstrapRelativePaths(context) {
147
- if (context.layout === "split") {
148
- return {
149
- markdown: "ops/bootstrap/agent-handoff.md",
150
- json: "ops/bootstrap/agent-handoff.json",
151
- intakeJson: "ops/bootstrap/intake.json",
152
- specDossier: "ops/bootstrap/spec-dossier.md",
153
- openQuestions: "ops/bootstrap/open-questions.md",
154
- qualityReport: "ops/bootstrap/quality-report.json",
155
- };
156
- }
157
-
158
- return {
159
- markdown: "bootstrap/agent-handoff.md",
160
- json: "bootstrap/agent-handoff.json",
161
- intakeJson: "bootstrap/intake.json",
162
- specDossier: "bootstrap/spec-dossier.md",
163
- openQuestions: "bootstrap/open-questions.md",
164
- qualityReport: "bootstrap/quality-report.json",
165
- };
166
- }
167
-
168
- function bootstrapFilePaths(context) {
169
- return {
170
- dir: context.paths.bootstrapDir,
171
- markdown: path.join(context.paths.bootstrapDir, "agent-handoff.md"),
172
- json: path.join(context.paths.bootstrapDir, "agent-handoff.json"),
173
- intakeJson: path.join(context.paths.bootstrapDir, "intake.json"),
174
- specDossier: path.join(context.paths.bootstrapDir, "spec-dossier.md"),
175
- openQuestions: path.join(context.paths.bootstrapDir, "open-questions.md"),
176
- qualityReport: path.join(context.paths.bootstrapDir, "quality-report.json"),
177
- };
178
- }
179
-
180
- function contractRelativePath(context) {
181
- return context.layout === "split"
182
- ? "ops/contract/operating-contract.json"
183
- : "contract/operating-contract.json";
184
- }
185
-
186
- function policyRelativePath(context) {
187
- return context.layout === "split"
188
- ? "ops/policy/autonomy.json"
189
- : "policy/autonomy.json";
190
- }
122
+ function unique(items) {
123
+ return [...new Set((items || []).map((item) => String(item || "").trim()).filter(Boolean))];
124
+ }
125
+
126
+ function normalizeEnum(value, allowed) {
127
+ const normalized = String(value || "").trim().toLowerCase().replace(/\s+/g, "_");
128
+ const alias = ENUM_ALIASES[normalized] || normalized;
129
+ return allowed.includes(alias) ? alias : null;
130
+ }
131
+
132
+ function choiceHint(allowed) {
133
+ if (allowed === TECHNICAL_LEVELS) return "low|medium|high|senior (o bajo|medio|alto)";
134
+ if (allowed === PROJECT_STATES) return "idea|draft|existing_repo|advanced";
135
+ if (allowed === DOC_STATES) return "none|notes|sos|spec_dossier|repo_docs";
136
+ if (allowed === DECISION_OWNERSHIPS) return "user|shared|agent (o usuario|compartido|agente)";
137
+ return Array.isArray(allowed) ? allowed.join("|") : null;
138
+ }
139
+
140
+ function explanationModeFor(technicalLevel) {
141
+ if (technicalLevel === "low") return "guided";
142
+ if (technicalLevel === "medium") return "balanced";
143
+ return "expert";
144
+ }
145
+
146
+ function bootstrapRelativePaths(context) {
147
+ if (context.layout === "split") {
148
+ return {
149
+ markdown: "ops/bootstrap/agent-handoff.md",
150
+ json: "ops/bootstrap/agent-handoff.json",
151
+ intakeJson: "ops/bootstrap/intake.json",
152
+ specDossier: "ops/bootstrap/spec-dossier.md",
153
+ openQuestions: "ops/bootstrap/open-questions.md",
154
+ qualityReport: "ops/bootstrap/quality-report.json",
155
+ };
156
+ }
157
+
158
+ return {
159
+ markdown: "bootstrap/agent-handoff.md",
160
+ json: "bootstrap/agent-handoff.json",
161
+ intakeJson: "bootstrap/intake.json",
162
+ specDossier: "bootstrap/spec-dossier.md",
163
+ openQuestions: "bootstrap/open-questions.md",
164
+ qualityReport: "bootstrap/quality-report.json",
165
+ };
166
+ }
167
+
168
+ function bootstrapFilePaths(context) {
169
+ return {
170
+ dir: context.paths.bootstrapDir,
171
+ markdown: path.join(context.paths.bootstrapDir, "agent-handoff.md"),
172
+ json: path.join(context.paths.bootstrapDir, "agent-handoff.json"),
173
+ intakeJson: path.join(context.paths.bootstrapDir, "intake.json"),
174
+ specDossier: path.join(context.paths.bootstrapDir, "spec-dossier.md"),
175
+ openQuestions: path.join(context.paths.bootstrapDir, "open-questions.md"),
176
+ qualityReport: path.join(context.paths.bootstrapDir, "quality-report.json"),
177
+ };
178
+ }
179
+
180
+ function contractRelativePath(context) {
181
+ return context.layout === "split"
182
+ ? "ops/contract/operating-contract.json"
183
+ : "contract/operating-contract.json";
184
+ }
185
+
186
+ function policyRelativePath(context) {
187
+ return context.layout === "split"
188
+ ? "ops/policy/autonomy.json"
189
+ : "policy/autonomy.json";
190
+ }
191
191
 
192
192
  function firstParagraph(text) {
193
193
  return String(text || "")
@@ -210,30 +210,30 @@ function inferServicesFromDependencies(pkg) {
210
210
  return SERVICE_HINTS.filter(([needle]) => names.some((name) => name.includes(needle))).map(([, label]) => label);
211
211
  }
212
212
 
213
- function scanProject(root) {
214
- const context = config.ensureContext(root);
215
- const scanRoot = context.appRoot;
216
- const envPaths = config.envFilePaths(context);
217
- const pkgPath = path.join(scanRoot, "package.json");
218
- const readmePath = fs.existsSync(path.join(scanRoot, "README.md"))
219
- ? path.join(scanRoot, "README.md")
220
- : fs.existsSync(path.join(scanRoot, "README.mdx"))
221
- ? path.join(scanRoot, "README.mdx")
222
- : "";
223
- const envExamplePath = fs.existsSync(envPaths.exampleFile)
224
- ? envPaths.exampleFile
225
- : fs.existsSync(envPaths.rootFile)
226
- ? envPaths.rootFile
227
- : "";
213
+ function scanProject(root) {
214
+ const context = config.ensureContext(root);
215
+ const scanRoot = context.appRoot;
216
+ const envPaths = config.envFilePaths(context);
217
+ const pkgPath = path.join(scanRoot, "package.json");
218
+ const readmePath = fs.existsSync(path.join(scanRoot, "README.md"))
219
+ ? path.join(scanRoot, "README.md")
220
+ : fs.existsSync(path.join(scanRoot, "README.mdx"))
221
+ ? path.join(scanRoot, "README.mdx")
222
+ : "";
223
+ const envExamplePath = fs.existsSync(envPaths.exampleFile)
224
+ ? envPaths.exampleFile
225
+ : fs.existsSync(envPaths.rootFile)
226
+ ? envPaths.rootFile
227
+ : "";
228
228
 
229
229
  const pkg = safeJson(readText(pkgPath));
230
230
  const readme = readText(readmePath);
231
231
  const envExample = readText(envExamplePath);
232
- const workflowsDir = path.join(context.workspaceRoot, ".github", "workflows");
232
+ const workflowsDir = path.join(context.workspaceRoot, ".github", "workflows");
233
233
  const workflowFiles = fs.existsSync(workflowsDir)
234
234
  ? fs.readdirSync(workflowsDir).filter((file) => file.endsWith(".yml") || file.endsWith(".yaml"))
235
235
  : [];
236
- const files = fs.readdirSync(scanRoot);
236
+ const files = fs.readdirSync(scanRoot);
237
237
  const stacks = [];
238
238
 
239
239
  if (pkg) stacks.push("node");
@@ -244,22 +244,22 @@ function scanProject(root) {
244
244
  if (files.includes("Dockerfile") || files.some((file) => file.startsWith("docker-compose"))) stacks.push("docker");
245
245
 
246
246
  const description = pkg?.description || firstParagraph(readme);
247
- const title = pkg?.displayName || pkg?.name || path.basename(scanRoot);
248
- const testCommands = unique([
249
- pkg?.scripts?.test ? "npm test" : "",
250
- fs.existsSync(path.join(scanRoot, "pytest.ini")) ? "pytest" : "",
251
- fs.existsSync(path.join(scanRoot, "Cargo.toml")) ? "cargo test" : "",
252
- fs.existsSync(path.join(scanRoot, "go.mod")) ? "go test ./..." : "",
253
- ]);
254
- const buildCommands = unique([
255
- pkg?.scripts?.build ? "npm run build" : "",
256
- fs.existsSync(path.join(scanRoot, "Dockerfile")) ? "docker build ." : "",
257
- ]);
247
+ const title = pkg?.displayName || pkg?.name || path.basename(scanRoot);
248
+ const testCommands = unique([
249
+ pkg?.scripts?.test ? "npm test" : "",
250
+ fs.existsSync(path.join(scanRoot, "pytest.ini")) ? "pytest" : "",
251
+ fs.existsSync(path.join(scanRoot, "Cargo.toml")) ? "cargo test" : "",
252
+ fs.existsSync(path.join(scanRoot, "go.mod")) ? "go test ./..." : "",
253
+ ]);
254
+ const buildCommands = unique([
255
+ pkg?.scripts?.build ? "npm run build" : "",
256
+ fs.existsSync(path.join(scanRoot, "Dockerfile")) ? "docker build ." : "",
257
+ ]);
258
258
  const services = unique([
259
259
  ...inferServicesFromDependencies(pkg),
260
260
  ...inferServicesFromEnv(envExample),
261
261
  ]);
262
- const gitRemote = git(["remote", "get-url", "origin"], context.workspaceRoot) || null;
262
+ const gitRemote = git(["remote", "get-url", "origin"], context.workspaceRoot) || null;
263
263
  const ciProviders = workflowFiles.length ? ["github-actions"] : [];
264
264
 
265
265
  return {
@@ -286,499 +286,505 @@ function normalizeList(value) {
286
286
  .filter(Boolean);
287
287
  }
288
288
 
289
- function parseJsonValue(value) {
290
- if (!String(value || "").trim()) return {};
291
- try {
292
- return JSON.parse(value);
293
- } catch (_error) {
294
- return {};
295
- }
296
- }
297
-
298
- function getBootstrapState(control, contextOrRoot) {
299
- const context = config.ensureContext(contextOrRoot);
300
- const bootstrap = control.meta?.opera?.bootstrap;
301
- if (!bootstrap) return null;
302
- const relative = bootstrapRelativePaths(context);
303
- return {
304
- ...bootstrap,
305
- handoffFiles: bootstrap.handoffFiles || {
306
- markdown: relative.markdown,
307
- json: relative.json,
308
- },
309
- intakeFiles: bootstrap.intakeFiles || {
310
- json: relative.intakeJson,
311
- specDossier: relative.specDossier,
312
- },
313
- reviewFiles: bootstrap.reviewFiles || {
314
- openQuestions: relative.openQuestions,
315
- qualityReport: relative.qualityReport,
316
- },
317
- };
318
- }
319
-
320
- function buildAvailableArtifacts(context, docsState) {
321
- const artifacts = [];
322
- if (docsState === "repo_docs") {
323
- const readmePath = path.join(context.appRoot, "README.md");
324
- if (fs.existsSync(readmePath)) {
325
- artifacts.push({
326
- kind: "readme",
327
- location: "repo",
328
- path: path.relative(context.workspaceRoot, readmePath).replace(/\\/g, "/"),
329
- });
330
- }
331
- }
332
- return artifacts;
333
- }
334
-
335
- function determineBootstrapMode(intake, requestedMode) {
336
- const normalizedRequested = normalizeEnum(requestedMode || "auto", BOOTSTRAP_MODES) || "auto";
337
-
338
- if (normalizedRequested === "handoff") {
339
- return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "forced_handoff" };
340
- }
341
-
342
- if (normalizedRequested === "direct") {
343
- const hasCore = intake.technicalLevel && intake.projectState && intake.documentationState && intake.decisionOwnership;
344
- return {
345
- requestedMode: normalizedRequested,
346
- mode: hasCore ? "direct_cli" : "agent_handoff",
347
- routeReason: hasCore ? "forced_direct" : "insufficient_docs",
348
- };
349
- }
350
-
351
- if (!["high", "senior"].includes(intake.technicalLevel)) {
352
- return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "non_technical_user" };
353
- }
354
- if (!["existing_repo", "advanced"].includes(intake.projectState)) {
355
- return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "idea_stage" };
356
- }
357
- if (!["sos", "spec_dossier", "repo_docs"].includes(intake.documentationState)) {
358
- return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "insufficient_docs" };
359
- }
360
- if (!["user", "shared"].includes(intake.decisionOwnership)) {
361
- return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "agent_owned_decisions" };
362
- }
363
- return { requestedMode: normalizedRequested, mode: "direct_cli", routeReason: "technical_existing_project" };
364
- }
365
-
366
- function directMissingFields(discovery) {
367
- const missing = [];
368
- if (!discovery.problemStatement) missing.push("problemStatement");
369
- if (!discovery.targetUser) missing.push("targetUser");
370
- if (!discovery.singularDesiredOutcome) missing.push("singularDesiredOutcome");
371
- if (!discovery.sourceOfTruth) missing.push("sourceOfTruth");
372
- if (!discovery.payload) missing.push("payload");
373
- if (!Object.keys(discovery.inputSchema || {}).length) missing.push("inputSchema");
374
- if (!Object.keys(discovery.outputSchema || {}).length) missing.push("outputSchema");
375
- return missing;
376
- }
377
-
378
- function buildHandoffPayload(control, profile, context) {
379
- const relative = bootstrapRelativePaths(context);
380
- return {
381
- version: 1,
382
- skill: "project-starter-skill",
383
- technicalLevel: profile.technicalLevel,
384
- explanationMode: explanationModeFor(profile.technicalLevel),
385
- decisionOwnership: profile.discovery?.decisionOwnership || profile.decisionOwnership || null,
386
- projectState: profile.projectState,
387
- documentationState: profile.documentationState,
388
- availableArtifacts: control.meta?.discovery?.availableArtifacts || [],
389
- problemStatement: profile.discovery?.problemStatement || "",
390
- targetUser: profile.discovery?.targetUser || "",
391
- singularDesiredOutcome: profile.discovery?.singularDesiredOutcome || "",
392
- files: {
393
- intakeJson: relative.intakeJson,
394
- specDossier: relative.specDossier,
395
- openQuestions: relative.openQuestions,
396
- },
397
- };
398
- }
399
-
400
- function buildHandoffPrompt(control, profile) {
401
- const lines = [
402
- "# TrackOps agent handoff",
403
- "",
404
- "Use `project-starter-skill` as a discovery and structuring skill for this project.",
405
- "",
406
- "## User profile",
407
- `- Technical level: ${profile.technicalLevel || "unknown"}`,
408
- `- Explanation mode: ${explanationModeFor(profile.technicalLevel)}`,
409
- `- Decision ownership: ${profile.discovery?.decisionOwnership || profile.decisionOwnership || "unknown"}`,
410
- "",
411
- "## Project state",
412
- `- Project state: ${profile.projectState || "unknown"}`,
413
- `- Documentation state: ${profile.documentationState || "unknown"}`,
414
- "",
415
- "## What to do",
416
- "- Start from the user, not from architecture assumptions.",
417
- "- Adapt depth and language to the user's technical level.",
418
- "- If documentation exists, read it, summarize it, and consolidate it.",
419
- "- If documentation does not exist, help the user turn the idea into a workable project specification.",
420
- "- Write `ops/bootstrap/intake.json` with the structured discovery output.",
421
- "- Write `ops/bootstrap/spec-dossier.md` with the narrative or technical specification that OPERA will ingest.",
422
- "- Write `ops/bootstrap/open-questions.md` if important uncertainties remain.",
423
- "- Include explicit fields for problem statement, target user, desired outcome, source of truth, delivery target, and schemas.",
424
- ];
425
-
426
- if (profile.discovery?.singularDesiredOutcome) {
427
- lines.push("", `Known project intention: ${profile.discovery.singularDesiredOutcome}`);
428
- }
429
-
430
- return `${lines.join("\n")}\n`;
431
- }
432
-
433
- async function askQuestion(rl, message, defaultValue) {
434
- const suffix = defaultValue ? ` (${defaultValue})` : "";
435
- const answer = await rl.question(`${message}${suffix}: `);
436
- return String(answer || "").trim() || String(defaultValue || "").trim();
437
- }
438
-
439
- async function askEnumQuestion(rl, message, defaultValue, allowed) {
440
- const hint = choiceHint(allowed);
441
- const suffix = defaultValue ? ` (${defaultValue})` : "";
442
- const answer = await rl.question(`${message}${hint ? ` [${hint}]` : ""}${suffix}: `);
443
- const normalized = normalizeEnum(answer || defaultValue, allowed);
444
- return normalized || normalizeEnum(defaultValue, allowed);
445
- }
446
-
447
- async function collectBootstrapProfile(root, control, options = {}) {
448
- const context = config.ensureContext(root);
449
- const locale = config.getLocale(control);
450
- setLocale(locale);
451
- const scan = scanProject(context);
452
- const previousUser = control.meta?.userProfile || {};
453
- const previousDiscovery = control.meta?.discovery || {};
454
- const previousBootstrap = getBootstrapState(control, context);
455
- const previous = previousBootstrap?.discovery || {};
456
- const interactive = options.interactive !== false && isInteractive();
457
-
458
- const defaults = {
459
- technicalLevel: normalizeEnum(options.technicalLevel || options.answers?.technicalLevel || previousUser.technicalLevel, TECHNICAL_LEVELS),
460
- projectState: normalizeEnum(options.projectState || options.answers?.projectState || previousDiscovery.projectState, PROJECT_STATES),
461
- documentationState: normalizeEnum(options.docsState || options.answers?.documentationState || previousDiscovery.documentationState, DOC_STATES),
462
- decisionOwnership: normalizeEnum(options.decisionOwnership || options.answers?.decisionOwnership || previous.decisionOwnership, DECISION_OWNERSHIPS),
463
- problemStatement: options.answers?.problemStatement || previous.problemStatement || "",
464
- targetUser: options.answers?.targetUser || previous.targetUser || "",
465
- singularDesiredOutcome:
466
- options.answers?.singularDesiredOutcome ||
467
- options.answers?.desiredOutcome ||
468
- previous.singularDesiredOutcome ||
469
- previous.desiredOutcome ||
470
- scan.description ||
471
- "",
472
- userLanguage: options.answers?.userLanguage || previous.userLanguage || locale,
473
- needsPlainLanguage: options.answers?.needsPlainLanguage ?? previous.needsPlainLanguage ?? false,
474
- recommendedStack: normalizeList(options.answers?.recommendedStack || previous.recommendedStack || scan.stacks),
475
- externalServices: normalizeList(options.answers?.externalServices || previous.externalServices || scan.services),
476
- sourceOfTruth: options.answers?.sourceOfTruth || previous.sourceOfTruth || scan.sourceOfTruthHint || "",
477
- payload: options.answers?.payload || previous.payload || scan.payloadHint || "",
478
- behaviorRules: normalizeList(options.answers?.behaviorRules || previous.behaviorRules || ""),
479
- inputSchema: options.answers?.inputSchema || previous.inputSchema || {},
480
- outputSchema: options.answers?.outputSchema || previous.outputSchema || {},
481
- architecturalInvariants: normalizeList(options.answers?.architecturalInvariants || previous.architecturalInvariants || ""),
482
- pipeline: normalizeList(options.answers?.pipeline || previous.pipeline || ""),
483
- templates: normalizeList(options.answers?.templates || previous.templates || ""),
484
- availableArtifacts: Array.isArray(previousDiscovery.availableArtifacts) ? previousDiscovery.availableArtifacts : [],
485
- };
486
-
487
- const answers = { ...defaults };
488
-
489
- if (interactive) {
490
- console.log("");
491
- console.log(t("bootstrap.header"));
492
- console.log(t("bootstrap.subtitle"));
493
- console.log(t("bootstrap.instructions"));
494
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
495
- try {
496
- answers.technicalLevel = await askEnumQuestion(rl, t("bootstrap.question.technicalLevel"), defaults.technicalLevel || "medium", TECHNICAL_LEVELS) || "medium";
497
- answers.projectState = await askEnumQuestion(rl, t("bootstrap.question.projectState"), defaults.projectState || "idea", PROJECT_STATES) || "idea";
498
- answers.documentationState = await askEnumQuestion(rl, t("bootstrap.question.docsState"), defaults.documentationState || "none", DOC_STATES) || "none";
499
- answers.decisionOwnership = await askEnumQuestion(rl, t("bootstrap.question.decisionOwnership"), defaults.decisionOwnership || "shared", DECISION_OWNERSHIPS) || "shared";
500
- } finally {
501
- rl.close();
502
- }
503
- }
504
-
505
- answers.availableArtifacts = answers.availableArtifacts.length
506
- ? answers.availableArtifacts
507
- : buildAvailableArtifacts(context, answers.documentationState);
508
-
509
- const routing = determineBootstrapMode(answers, options.bootstrapMode);
510
-
511
- if (routing.mode === "agent_handoff") {
512
- return {
513
- version: 2,
514
- status: "awaiting_agent",
515
- mode: "agent_handoff",
516
- routeReason: routing.routeReason,
517
- technicalLevel: answers.technicalLevel,
518
- projectState: answers.projectState,
519
- documentationState: answers.documentationState,
520
- decisionOwnership: answers.decisionOwnership,
521
- startedAt: previousBootstrap?.startedAt || nowIso(),
522
- completedAt: null,
523
- missingFields: ["intakeJson", "specDossier"],
524
- discovery: answers,
525
- inference: scan,
526
- };
527
- }
528
-
529
- if (interactive) {
530
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
531
- try {
532
- answers.problemStatement = await askQuestion(rl, t("bootstrap.question.problemStatement"), defaults.problemStatement);
533
- answers.targetUser = await askQuestion(rl, t("bootstrap.question.targetUser"), defaults.targetUser);
534
- answers.singularDesiredOutcome = await askQuestion(rl, t("bootstrap.question.desiredOutcome"), defaults.singularDesiredOutcome);
535
- answers.externalServices = normalizeList(await askQuestion(rl, t("bootstrap.question.externalServices"), defaults.externalServices.join(", ")));
536
- answers.sourceOfTruth = await askQuestion(rl, t("bootstrap.question.sourceOfTruth"), defaults.sourceOfTruth);
537
- answers.payload = await askQuestion(rl, t("bootstrap.question.payload"), defaults.payload);
538
- answers.behaviorRules = normalizeList(await askQuestion(rl, t("bootstrap.question.behaviorRules"), defaults.behaviorRules.join("; ")));
539
- answers.inputSchema = parseJsonValue(await askQuestion(rl, t("bootstrap.question.inputSchema"), JSON.stringify(defaults.inputSchema)));
540
- answers.outputSchema = parseJsonValue(await askQuestion(rl, t("bootstrap.question.outputSchema"), JSON.stringify(defaults.outputSchema)));
541
- answers.architecturalInvariants = normalizeList(await askQuestion(rl, t("bootstrap.question.invariants"), defaults.architecturalInvariants.join("; ")));
542
- answers.pipeline = normalizeList(await askQuestion(rl, t("bootstrap.question.pipeline"), defaults.pipeline.join("; ")));
543
- answers.templates = normalizeList(await askQuestion(rl, t("bootstrap.question.templates"), defaults.templates.join(", ")));
544
- } finally {
545
- rl.close();
546
- }
547
- }
548
-
549
- const missingFields = directMissingFields(answers);
550
- return {
551
- version: 2,
552
- status: missingFields.length ? "awaiting_intake" : "completed",
553
- mode: "direct_cli",
554
- routeReason: routing.routeReason,
555
- technicalLevel: answers.technicalLevel,
556
- projectState: answers.projectState,
557
- documentationState: answers.documentationState,
558
- decisionOwnership: answers.decisionOwnership,
559
- startedAt: previousBootstrap?.startedAt || nowIso(),
560
- completedAt: missingFields.length ? null : nowIso(),
561
- missingFields,
562
- discovery: answers,
563
- inference: scan,
564
- };
565
- }
566
-
567
- function isVirginGenesis(content) {
568
- const text = String(content || "");
569
- return !text.trim() || /TODO:/i.test(text) || /The Constitution of the project/i.test(text) || /La Constitución del proyecto/i.test(text);
570
- }
571
-
572
- function parseSpecSections(specText) {
573
- const sections = {};
574
- const lines = String(specText || "").split(/\r?\n/);
575
- let current = null;
576
- for (const line of lines) {
577
- const heading = line.match(/^##\s+(.+?)\s*$/);
578
- if (heading) {
579
- current = heading[1].trim().toLowerCase();
580
- sections[current] = [];
581
- continue;
582
- }
583
- if (current) sections[current].push(line);
584
- }
585
- return Object.fromEntries(
586
- Object.entries(sections).map(([key, value]) => [key, value.join("\n").trim()]),
587
- );
588
- }
589
-
590
- function buildOpenQuestions(missingFields, contradictions) {
591
- const lines = ["# Open questions", ""];
592
- if (missingFields.length) {
593
- lines.push("## Missing fields", "");
594
- missingFields.forEach((field) => lines.push(`- ${field}`));
595
- lines.push("");
596
- }
597
- if (contradictions.length) {
598
- lines.push("## Contradictions", "");
599
- contradictions.forEach((item) => lines.push(`- ${item}`));
600
- lines.push("");
601
- }
602
- if (!missingFields.length && !contradictions.length) {
603
- lines.push("- None.");
604
- }
605
- return `${lines.join("\n")}\n`;
606
- }
607
-
608
- function qualityStatusFor(missingFields, contradictions) {
609
- if (missingFields.length >= 2) return "blocked";
610
- if (missingFields.length || contradictions.length) return "needs_review";
611
- return "ready";
612
- }
613
-
614
- function contractReadinessFor(profile, qualityStatus) {
615
- if (qualityStatus === "blocked") return "hypothesis";
616
- if (qualityStatus === "needs_review") return "provisional";
617
- if (
618
- ["sos", "spec_dossier"].includes(profile.documentationState) &&
619
- profile.discovery?.decisionOwnership === "user" &&
620
- profile.mode === "direct_cli"
621
- ) {
622
- return "locked";
623
- }
624
- return "verified";
625
- }
626
-
627
- function buildQualityReport(context, profile, specText) {
628
- const sections = parseSpecSections(specText);
629
- const missingFields = directMissingFields(profile.discovery);
630
- if (!profile.discovery.decisionOwnership) missingFields.push("decisionOwnership");
631
- if (!profile.discovery.problemStatement) missingFields.push("problemStatement");
632
- if (!profile.discovery.targetUser) missingFields.push("targetUser");
633
-
634
- const contradictions = [];
635
- const mappings = [
636
- ["problem statement", "problemStatement", profile.discovery.problemStatement],
637
- ["target user", "targetUser", profile.discovery.targetUser],
638
- ["singular desired outcome", "singularDesiredOutcome", profile.discovery.singularDesiredOutcome],
639
- ["delivery target", "payload", profile.discovery.payload],
640
- ["source of truth", "sourceOfTruth", profile.discovery.sourceOfTruth],
641
- ];
642
- for (const [sectionName, fieldName, expected] of mappings) {
643
- const actual = sections[sectionName];
644
- if (!actual) {
645
- missingFields.push(fieldName);
646
- continue;
647
- }
648
- if (expected && actual && normalizeText(actual) !== normalizeText(expected)) {
649
- contradictions.push(`${sectionName}: spec dossier and intake disagree`);
650
- }
651
- }
652
-
653
- const status = qualityStatusFor(unique(missingFields), contradictions);
654
- const contractReadiness = contractReadinessFor(profile, status);
655
- const report = {
656
- version: 1,
657
- status,
658
- missingFields: unique(missingFields),
659
- contradictions,
660
- contractReadiness,
661
- validatedAt: nowIso(),
662
- handoffMode: profile.mode,
663
- };
664
- const files = bootstrapFilePaths(context);
665
- writeJson(files.qualityReport, report);
666
- writeText(files.openQuestions, buildOpenQuestions(report.missingFields, report.contradictions));
667
- return report;
668
- }
669
-
670
- function normalizeText(value) {
671
- return String(value || "").trim().replace(/\s+/g, " ").toLowerCase();
672
- }
673
-
674
- function buildOperatingContract(control, profile, qualityReport, context) {
675
- const discovery = profile.discovery || {};
676
- return {
677
- version: CONTRACT_VERSION,
678
- intent: {
679
- problemStatement: discovery.problemStatement || "",
680
- targetUser: discovery.targetUser || "",
681
- singularDesiredOutcome: discovery.singularDesiredOutcome || "",
682
- deliveryTarget: discovery.payload || "",
683
- },
684
- userModel: {
685
- technicalLevel: profile.technicalLevel || null,
686
- explanationMode: explanationModeFor(profile.technicalLevel),
687
- decisionOwnership: discovery.decisionOwnership || profile.decisionOwnership || null,
688
- language: discovery.userLanguage || config.getLocale(control),
689
- needsPlainLanguage: Boolean(discovery.needsPlainLanguage || profile.technicalLevel === "low"),
690
- },
691
- evidence: {
692
- projectState: profile.projectState || null,
693
- documentationState: profile.documentationState || null,
694
- sourceArtifacts: discovery.availableArtifacts || [],
695
- repoScan: profile.inference || {},
696
- },
697
- system: {
698
- sourceOfTruth: discovery.sourceOfTruth || "",
699
- externalServices: discovery.externalServices || [],
700
- inputSchema: discovery.inputSchema || {},
701
- outputSchema: discovery.outputSchema || {},
702
- behaviorRules: discovery.behaviorRules || [],
703
- architecturalInvariants: discovery.architecturalInvariants || [],
704
- },
705
- execution: {
706
- pipeline: discovery.pipeline || [],
707
- templates: discovery.templates || [],
708
- phaseModel: "opera-v3",
709
- taskSeeds: buildSeedTasks(control, profile).map((task) => task.id),
710
- },
711
- governance: {
712
- policyFile: policyRelativePath(context),
713
- riskProfile: "standard",
714
- approvalRules: [
715
- "destructive_changes_require_approval",
716
- "production_deploy_requires_approval",
717
- "external_side_effects_require_approval",
718
- ],
719
- },
720
- quality: {
721
- contractReadiness: qualityReport.contractReadiness,
722
- openQuestions: qualityReport.missingFields.concat(qualityReport.contradictions),
723
- lastValidatedAt: qualityReport.validatedAt,
724
- },
725
- };
726
- }
727
-
728
- function writeAutonomyPolicy(context) {
729
- const payload = {
730
- version: 1,
731
- defaultRiskProfile: "standard",
732
- levels: {
733
- green: ["read_files", "run_tests", "update_operational_docs", "write_tmp_files"],
734
- yellow: ["install_dependencies", "change_structure", "modify_pipeline"],
735
- red: ["delete_persistent_data", "deploy_to_production", "external_side_effects", "security_changes"],
736
- },
737
- approvalRules: {
738
- destructive_changes_require_approval: true,
739
- production_deploy_requires_approval: true,
740
- external_side_effects_require_approval: true,
741
- },
742
- };
743
- writeJson(context.paths.autonomyPolicyFile, payload);
744
- }
745
-
746
- function renderGenesis(control, contract) {
747
- const locale = config.getLocale(control);
748
- const templatePath = resolveLocalizedFile(TEMPLATES_DIR, locale, "genesis.md");
749
- let content = fs.readFileSync(templatePath, "utf8");
750
- const rules = (contract.system.behaviorRules || []).map((item) => `- ${item}`).join("\n") || `- ${t("bootstrap.noneDefined")}`;
751
- const invariants = (contract.system.architecturalInvariants || []).map((item) => `- ${item}`).join("\n") || `- ${t("bootstrap.noneDefined")}`;
752
- const services = (contract.system.externalServices || []).length
753
- ? contract.system.externalServices.map((item) => `| ${item} | ${t("bootstrap.servicePending")} | ${t("bootstrap.servicePending")} |`).join("\n")
754
- : `| | | — |`;
755
- const pipeline = (contract.execution.pipeline || []).length
756
- ? contract.execution.pipeline.map((item) => `- ${item}`).join("\n")
757
- : `- ${t("bootstrap.noneDefined")}`;
758
- const templates = (contract.execution.templates || []).length
759
- ? contract.execution.templates.map((item) => `- \`${item}\``).join("\n")
760
- : `- ${t("bootstrap.noneDefined")}`;
761
- const schema = JSON.stringify({
762
- input: {
763
- source: contract.system.sourceOfTruth,
764
- schema: contract.system.inputSchema || {},
765
- },
766
- output: {
767
- destination: contract.intent.deliveryTarget,
768
- schema: contract.system.outputSchema || {},
769
- },
770
- }, null, 2);
771
-
772
- const replacements = {
773
- PROJECT_NAME: control.meta.projectName || "Project",
774
- DESIRED_OUTCOME: contract.intent.singularDesiredOutcome || t("bootstrap.pendingValue"),
775
- SERVICES_TABLE: services,
776
- SOURCE_OF_TRUTH: contract.system.sourceOfTruth || t("bootstrap.pendingValue"),
777
- PAYLOAD: contract.intent.deliveryTarget || t("bootstrap.pendingValue"),
778
- BEHAVIOR_RULES: rules,
779
- DATA_SCHEMA: schema,
780
- ARCHITECTURAL_INVARIANTS: invariants,
781
- PIPELINE_ITEMS: pipeline,
289
+ function parseJsonValue(value) {
290
+ if (!String(value || "").trim()) return {};
291
+ try {
292
+ return JSON.parse(value);
293
+ } catch (_error) {
294
+ return {};
295
+ }
296
+ }
297
+
298
+ function getBootstrapState(control, contextOrRoot) {
299
+ const context = config.ensureContext(contextOrRoot);
300
+ const bootstrap = control.meta?.opera?.bootstrap;
301
+ if (!bootstrap) return null;
302
+ const relative = bootstrapRelativePaths(context);
303
+ return {
304
+ ...bootstrap,
305
+ handoffFiles: bootstrap.handoffFiles || {
306
+ markdown: relative.markdown,
307
+ json: relative.json,
308
+ },
309
+ intakeFiles: bootstrap.intakeFiles || {
310
+ json: relative.intakeJson,
311
+ specDossier: relative.specDossier,
312
+ },
313
+ reviewFiles: bootstrap.reviewFiles || {
314
+ openQuestions: relative.openQuestions,
315
+ qualityReport: relative.qualityReport,
316
+ },
317
+ };
318
+ }
319
+
320
+ function buildAvailableArtifacts(context, docsState) {
321
+ const artifacts = [];
322
+ if (docsState === "repo_docs") {
323
+ const readmePath = path.join(context.appRoot, "README.md");
324
+ if (fs.existsSync(readmePath)) {
325
+ artifacts.push({
326
+ kind: "readme",
327
+ location: "repo",
328
+ path: path.relative(context.workspaceRoot, readmePath).replace(/\\/g, "/"),
329
+ });
330
+ }
331
+ }
332
+ return artifacts;
333
+ }
334
+
335
+ function determineBootstrapMode(intake, requestedMode) {
336
+ const normalizedRequested = normalizeEnum(requestedMode || "auto", BOOTSTRAP_MODES) || "auto";
337
+
338
+ if (normalizedRequested === "handoff") {
339
+ return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "forced_handoff" };
340
+ }
341
+
342
+ if (normalizedRequested === "direct") {
343
+ const hasCore = intake.technicalLevel && intake.projectState && intake.documentationState && intake.decisionOwnership;
344
+ return {
345
+ requestedMode: normalizedRequested,
346
+ mode: hasCore ? "direct_cli" : "agent_handoff",
347
+ routeReason: hasCore ? "forced_direct" : "insufficient_docs",
348
+ };
349
+ }
350
+
351
+ if (!["high", "senior"].includes(intake.technicalLevel)) {
352
+ return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "non_technical_user" };
353
+ }
354
+ if (!["existing_repo", "advanced"].includes(intake.projectState)) {
355
+ return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "idea_stage" };
356
+ }
357
+ if (!["sos", "spec_dossier", "repo_docs"].includes(intake.documentationState)) {
358
+ return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "insufficient_docs" };
359
+ }
360
+ if (!["user", "shared"].includes(intake.decisionOwnership)) {
361
+ return { requestedMode: normalizedRequested, mode: "agent_handoff", routeReason: "agent_owned_decisions" };
362
+ }
363
+ return { requestedMode: normalizedRequested, mode: "direct_cli", routeReason: "technical_existing_project" };
364
+ }
365
+
366
+ function directMissingFields(discovery) {
367
+ const missing = [];
368
+ if (!discovery.problemStatement) missing.push("problemStatement");
369
+ if (!discovery.targetUser) missing.push("targetUser");
370
+ if (!discovery.singularDesiredOutcome) missing.push("singularDesiredOutcome");
371
+ if (!discovery.sourceOfTruth) missing.push("sourceOfTruth");
372
+ if (!discovery.payload) missing.push("payload");
373
+ if (!Object.keys(discovery.inputSchema || {}).length) missing.push("inputSchema");
374
+ if (!Object.keys(discovery.outputSchema || {}).length) missing.push("outputSchema");
375
+ return missing;
376
+ }
377
+
378
+ function buildHandoffPayload(control, profile, context) {
379
+ const relative = bootstrapRelativePaths(context);
380
+ const locale = config.getLocale(control);
381
+ return {
382
+ version: 1,
383
+ skill: "project-starter-skill",
384
+ locale,
385
+ technicalLevel: profile.technicalLevel,
386
+ explanationMode: explanationModeFor(profile.technicalLevel),
387
+ decisionOwnership: profile.discovery?.decisionOwnership || profile.decisionOwnership || null,
388
+ projectState: profile.projectState,
389
+ documentationState: profile.documentationState,
390
+ availableArtifacts: control.meta?.discovery?.availableArtifacts || [],
391
+ problemStatement: profile.discovery?.problemStatement || "",
392
+ targetUser: profile.discovery?.targetUser || "",
393
+ singularDesiredOutcome: profile.discovery?.singularDesiredOutcome || "",
394
+ files: {
395
+ intakeJson: relative.intakeJson,
396
+ specDossier: relative.specDossier,
397
+ openQuestions: relative.openQuestions,
398
+ },
399
+ };
400
+ }
401
+
402
+ function buildHandoffPrompt(control, profile) {
403
+ const locale = config.getLocale(control);
404
+ const languageName = locale === "es" ? "Spanish" : "English";
405
+ const lines = [
406
+ `# ${t("handoff.title")}`,
407
+ "",
408
+ t("handoff.skillInstruction"),
409
+ "",
410
+ `## ${t("handoff.section.userProfile")}`,
411
+ `- ${t("handoff.label.technicalLevel")}: ${profile.technicalLevel || "unknown"}`,
412
+ `- ${t("handoff.label.explanationMode")}: ${explanationModeFor(profile.technicalLevel)}`,
413
+ `- ${t("handoff.label.decisionOwnership")}: ${profile.discovery?.decisionOwnership || profile.decisionOwnership || "unknown"}`,
414
+ `- ${t("handoff.label.preferredLanguage")}: ${languageName} (${locale})`,
415
+ "",
416
+ `## ${t("handoff.section.projectState")}`,
417
+ `- ${t("handoff.label.projectState")}: ${profile.projectState || "unknown"}`,
418
+ `- ${t("handoff.label.documentationState")}: ${profile.documentationState || "unknown"}`,
419
+ "",
420
+ `## ${t("handoff.section.whatToDo")}`,
421
+ `- ${t("handoff.instruction.startFromUser")}`,
422
+ `- ${t("handoff.instruction.adaptDepth")}`,
423
+ `- ${t("handoff.instruction.readDocs")}`,
424
+ `- ${t("handoff.instruction.helpSpec")}`,
425
+ `- ${t("handoff.instruction.writeIntake")}`,
426
+ `- ${t("handoff.instruction.writeSpec")}`,
427
+ `- ${t("handoff.instruction.writeQuestions")}`,
428
+ `- ${t("handoff.instruction.includeFields")}`,
429
+ `- ${t("handoff.instruction.respondInLanguage", { language: languageName })}`,
430
+ ];
431
+
432
+ if (profile.discovery?.singularDesiredOutcome) {
433
+ lines.push("", `${t("handoff.label.knownIntention")}: ${profile.discovery.singularDesiredOutcome}`);
434
+ }
435
+
436
+ return `${lines.join("\n")}\n`;
437
+ }
438
+
439
+ async function askQuestion(rl, message, defaultValue) {
440
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
441
+ const answer = await rl.question(`${message}${suffix}: `);
442
+ return String(answer || "").trim() || String(defaultValue || "").trim();
443
+ }
444
+
445
+ async function askEnumQuestion(rl, message, defaultValue, allowed) {
446
+ const hint = choiceHint(allowed);
447
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
448
+ const answer = await rl.question(`${message}${hint ? ` [${hint}]` : ""}${suffix}: `);
449
+ const normalized = normalizeEnum(answer || defaultValue, allowed);
450
+ return normalized || normalizeEnum(defaultValue, allowed);
451
+ }
452
+
453
+ async function collectBootstrapProfile(root, control, options = {}) {
454
+ const context = config.ensureContext(root);
455
+ const locale = config.getLocale(control);
456
+ setLocale(locale);
457
+ const scan = scanProject(context);
458
+ const previousUser = control.meta?.userProfile || {};
459
+ const previousDiscovery = control.meta?.discovery || {};
460
+ const previousBootstrap = getBootstrapState(control, context);
461
+ const previous = previousBootstrap?.discovery || {};
462
+ const interactive = options.interactive !== false && isInteractive();
463
+
464
+ const defaults = {
465
+ technicalLevel: normalizeEnum(options.technicalLevel || options.answers?.technicalLevel || previousUser.technicalLevel, TECHNICAL_LEVELS),
466
+ projectState: normalizeEnum(options.projectState || options.answers?.projectState || previousDiscovery.projectState, PROJECT_STATES),
467
+ documentationState: normalizeEnum(options.docsState || options.answers?.documentationState || previousDiscovery.documentationState, DOC_STATES),
468
+ decisionOwnership: normalizeEnum(options.decisionOwnership || options.answers?.decisionOwnership || previous.decisionOwnership, DECISION_OWNERSHIPS),
469
+ problemStatement: options.answers?.problemStatement || previous.problemStatement || "",
470
+ targetUser: options.answers?.targetUser || previous.targetUser || "",
471
+ singularDesiredOutcome:
472
+ options.answers?.singularDesiredOutcome ||
473
+ options.answers?.desiredOutcome ||
474
+ previous.singularDesiredOutcome ||
475
+ previous.desiredOutcome ||
476
+ scan.description ||
477
+ "",
478
+ userLanguage: options.answers?.userLanguage || previous.userLanguage || locale,
479
+ needsPlainLanguage: options.answers?.needsPlainLanguage ?? previous.needsPlainLanguage ?? false,
480
+ recommendedStack: normalizeList(options.answers?.recommendedStack || previous.recommendedStack || scan.stacks),
481
+ externalServices: normalizeList(options.answers?.externalServices || previous.externalServices || scan.services),
482
+ sourceOfTruth: options.answers?.sourceOfTruth || previous.sourceOfTruth || scan.sourceOfTruthHint || "",
483
+ payload: options.answers?.payload || previous.payload || scan.payloadHint || "",
484
+ behaviorRules: normalizeList(options.answers?.behaviorRules || previous.behaviorRules || ""),
485
+ inputSchema: options.answers?.inputSchema || previous.inputSchema || {},
486
+ outputSchema: options.answers?.outputSchema || previous.outputSchema || {},
487
+ architecturalInvariants: normalizeList(options.answers?.architecturalInvariants || previous.architecturalInvariants || ""),
488
+ pipeline: normalizeList(options.answers?.pipeline || previous.pipeline || ""),
489
+ templates: normalizeList(options.answers?.templates || previous.templates || ""),
490
+ availableArtifacts: Array.isArray(previousDiscovery.availableArtifacts) ? previousDiscovery.availableArtifacts : [],
491
+ };
492
+
493
+ const answers = { ...defaults };
494
+
495
+ if (interactive) {
496
+ console.log("");
497
+ console.log(t("bootstrap.header"));
498
+ console.log(t("bootstrap.subtitle"));
499
+ console.log(t("bootstrap.instructions"));
500
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
501
+ try {
502
+ answers.technicalLevel = await askEnumQuestion(rl, t("bootstrap.question.technicalLevel"), defaults.technicalLevel || "medium", TECHNICAL_LEVELS) || "medium";
503
+ answers.projectState = await askEnumQuestion(rl, t("bootstrap.question.projectState"), defaults.projectState || "idea", PROJECT_STATES) || "idea";
504
+ answers.documentationState = await askEnumQuestion(rl, t("bootstrap.question.docsState"), defaults.documentationState || "none", DOC_STATES) || "none";
505
+ answers.decisionOwnership = await askEnumQuestion(rl, t("bootstrap.question.decisionOwnership"), defaults.decisionOwnership || "shared", DECISION_OWNERSHIPS) || "shared";
506
+ } finally {
507
+ rl.close();
508
+ }
509
+ }
510
+
511
+ answers.availableArtifacts = answers.availableArtifacts.length
512
+ ? answers.availableArtifacts
513
+ : buildAvailableArtifacts(context, answers.documentationState);
514
+
515
+ const routing = determineBootstrapMode(answers, options.bootstrapMode);
516
+
517
+ if (routing.mode === "agent_handoff") {
518
+ return {
519
+ version: 2,
520
+ status: "awaiting_agent",
521
+ mode: "agent_handoff",
522
+ routeReason: routing.routeReason,
523
+ technicalLevel: answers.technicalLevel,
524
+ projectState: answers.projectState,
525
+ documentationState: answers.documentationState,
526
+ decisionOwnership: answers.decisionOwnership,
527
+ startedAt: previousBootstrap?.startedAt || nowIso(),
528
+ completedAt: null,
529
+ missingFields: ["intakeJson", "specDossier"],
530
+ discovery: answers,
531
+ inference: scan,
532
+ };
533
+ }
534
+
535
+ if (interactive) {
536
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
537
+ try {
538
+ answers.problemStatement = await askQuestion(rl, t("bootstrap.question.problemStatement"), defaults.problemStatement);
539
+ answers.targetUser = await askQuestion(rl, t("bootstrap.question.targetUser"), defaults.targetUser);
540
+ answers.singularDesiredOutcome = await askQuestion(rl, t("bootstrap.question.desiredOutcome"), defaults.singularDesiredOutcome);
541
+ answers.externalServices = normalizeList(await askQuestion(rl, t("bootstrap.question.externalServices"), defaults.externalServices.join(", ")));
542
+ answers.sourceOfTruth = await askQuestion(rl, t("bootstrap.question.sourceOfTruth"), defaults.sourceOfTruth);
543
+ answers.payload = await askQuestion(rl, t("bootstrap.question.payload"), defaults.payload);
544
+ answers.behaviorRules = normalizeList(await askQuestion(rl, t("bootstrap.question.behaviorRules"), defaults.behaviorRules.join("; ")));
545
+ answers.inputSchema = parseJsonValue(await askQuestion(rl, t("bootstrap.question.inputSchema"), JSON.stringify(defaults.inputSchema)));
546
+ answers.outputSchema = parseJsonValue(await askQuestion(rl, t("bootstrap.question.outputSchema"), JSON.stringify(defaults.outputSchema)));
547
+ answers.architecturalInvariants = normalizeList(await askQuestion(rl, t("bootstrap.question.invariants"), defaults.architecturalInvariants.join("; ")));
548
+ answers.pipeline = normalizeList(await askQuestion(rl, t("bootstrap.question.pipeline"), defaults.pipeline.join("; ")));
549
+ answers.templates = normalizeList(await askQuestion(rl, t("bootstrap.question.templates"), defaults.templates.join(", ")));
550
+ } finally {
551
+ rl.close();
552
+ }
553
+ }
554
+
555
+ const missingFields = directMissingFields(answers);
556
+ return {
557
+ version: 2,
558
+ status: missingFields.length ? "awaiting_intake" : "completed",
559
+ mode: "direct_cli",
560
+ routeReason: routing.routeReason,
561
+ technicalLevel: answers.technicalLevel,
562
+ projectState: answers.projectState,
563
+ documentationState: answers.documentationState,
564
+ decisionOwnership: answers.decisionOwnership,
565
+ startedAt: previousBootstrap?.startedAt || nowIso(),
566
+ completedAt: missingFields.length ? null : nowIso(),
567
+ missingFields,
568
+ discovery: answers,
569
+ inference: scan,
570
+ };
571
+ }
572
+
573
+ function isVirginGenesis(content) {
574
+ const text = String(content || "");
575
+ return !text.trim() || /TODO:/i.test(text) || /The Constitution of the project/i.test(text) || /La Constitución del proyecto/i.test(text);
576
+ }
577
+
578
+ function parseSpecSections(specText) {
579
+ const sections = {};
580
+ const lines = String(specText || "").split(/\r?\n/);
581
+ let current = null;
582
+ for (const line of lines) {
583
+ const heading = line.match(/^##\s+(.+?)\s*$/);
584
+ if (heading) {
585
+ current = heading[1].trim().toLowerCase();
586
+ sections[current] = [];
587
+ continue;
588
+ }
589
+ if (current) sections[current].push(line);
590
+ }
591
+ return Object.fromEntries(
592
+ Object.entries(sections).map(([key, value]) => [key, value.join("\n").trim()]),
593
+ );
594
+ }
595
+
596
+ function buildOpenQuestions(missingFields, contradictions) {
597
+ const lines = ["# Open questions", ""];
598
+ if (missingFields.length) {
599
+ lines.push("## Missing fields", "");
600
+ missingFields.forEach((field) => lines.push(`- ${field}`));
601
+ lines.push("");
602
+ }
603
+ if (contradictions.length) {
604
+ lines.push("## Contradictions", "");
605
+ contradictions.forEach((item) => lines.push(`- ${item}`));
606
+ lines.push("");
607
+ }
608
+ if (!missingFields.length && !contradictions.length) {
609
+ lines.push("- None.");
610
+ }
611
+ return `${lines.join("\n")}\n`;
612
+ }
613
+
614
+ function qualityStatusFor(missingFields, contradictions) {
615
+ if (missingFields.length >= 2) return "blocked";
616
+ if (missingFields.length || contradictions.length) return "needs_review";
617
+ return "ready";
618
+ }
619
+
620
+ function contractReadinessFor(profile, qualityStatus) {
621
+ if (qualityStatus === "blocked") return "hypothesis";
622
+ if (qualityStatus === "needs_review") return "provisional";
623
+ if (
624
+ ["sos", "spec_dossier"].includes(profile.documentationState) &&
625
+ profile.discovery?.decisionOwnership === "user" &&
626
+ profile.mode === "direct_cli"
627
+ ) {
628
+ return "locked";
629
+ }
630
+ return "verified";
631
+ }
632
+
633
+ function buildQualityReport(context, profile, specText) {
634
+ const sections = parseSpecSections(specText);
635
+ const missingFields = directMissingFields(profile.discovery);
636
+ if (!profile.discovery.decisionOwnership) missingFields.push("decisionOwnership");
637
+ if (!profile.discovery.problemStatement) missingFields.push("problemStatement");
638
+ if (!profile.discovery.targetUser) missingFields.push("targetUser");
639
+
640
+ const contradictions = [];
641
+ const mappings = [
642
+ ["problem statement", "problemStatement", profile.discovery.problemStatement],
643
+ ["target user", "targetUser", profile.discovery.targetUser],
644
+ ["singular desired outcome", "singularDesiredOutcome", profile.discovery.singularDesiredOutcome],
645
+ ["delivery target", "payload", profile.discovery.payload],
646
+ ["source of truth", "sourceOfTruth", profile.discovery.sourceOfTruth],
647
+ ];
648
+ for (const [sectionName, fieldName, expected] of mappings) {
649
+ const actual = sections[sectionName];
650
+ if (!actual) {
651
+ missingFields.push(fieldName);
652
+ continue;
653
+ }
654
+ if (expected && actual && normalizeText(actual) !== normalizeText(expected)) {
655
+ contradictions.push(`${sectionName}: spec dossier and intake disagree`);
656
+ }
657
+ }
658
+
659
+ const status = qualityStatusFor(unique(missingFields), contradictions);
660
+ const contractReadiness = contractReadinessFor(profile, status);
661
+ const report = {
662
+ version: 1,
663
+ status,
664
+ missingFields: unique(missingFields),
665
+ contradictions,
666
+ contractReadiness,
667
+ validatedAt: nowIso(),
668
+ handoffMode: profile.mode,
669
+ };
670
+ const files = bootstrapFilePaths(context);
671
+ writeJson(files.qualityReport, report);
672
+ writeText(files.openQuestions, buildOpenQuestions(report.missingFields, report.contradictions));
673
+ return report;
674
+ }
675
+
676
+ function normalizeText(value) {
677
+ return String(value || "").trim().replace(/\s+/g, " ").toLowerCase();
678
+ }
679
+
680
+ function buildOperatingContract(control, profile, qualityReport, context) {
681
+ const discovery = profile.discovery || {};
682
+ return {
683
+ version: CONTRACT_VERSION,
684
+ intent: {
685
+ problemStatement: discovery.problemStatement || "",
686
+ targetUser: discovery.targetUser || "",
687
+ singularDesiredOutcome: discovery.singularDesiredOutcome || "",
688
+ deliveryTarget: discovery.payload || "",
689
+ },
690
+ userModel: {
691
+ technicalLevel: profile.technicalLevel || null,
692
+ explanationMode: explanationModeFor(profile.technicalLevel),
693
+ decisionOwnership: discovery.decisionOwnership || profile.decisionOwnership || null,
694
+ language: discovery.userLanguage || config.getLocale(control),
695
+ needsPlainLanguage: Boolean(discovery.needsPlainLanguage || profile.technicalLevel === "low"),
696
+ },
697
+ evidence: {
698
+ projectState: profile.projectState || null,
699
+ documentationState: profile.documentationState || null,
700
+ sourceArtifacts: discovery.availableArtifacts || [],
701
+ repoScan: profile.inference || {},
702
+ },
703
+ system: {
704
+ sourceOfTruth: discovery.sourceOfTruth || "",
705
+ externalServices: discovery.externalServices || [],
706
+ inputSchema: discovery.inputSchema || {},
707
+ outputSchema: discovery.outputSchema || {},
708
+ behaviorRules: discovery.behaviorRules || [],
709
+ architecturalInvariants: discovery.architecturalInvariants || [],
710
+ },
711
+ execution: {
712
+ pipeline: discovery.pipeline || [],
713
+ templates: discovery.templates || [],
714
+ phaseModel: "opera-v3",
715
+ taskSeeds: buildSeedTasks(control, profile).map((task) => task.id),
716
+ },
717
+ governance: {
718
+ policyFile: policyRelativePath(context),
719
+ riskProfile: "standard",
720
+ approvalRules: [
721
+ "destructive_changes_require_approval",
722
+ "production_deploy_requires_approval",
723
+ "external_side_effects_require_approval",
724
+ ],
725
+ },
726
+ quality: {
727
+ contractReadiness: qualityReport.contractReadiness,
728
+ openQuestions: qualityReport.missingFields.concat(qualityReport.contradictions),
729
+ lastValidatedAt: qualityReport.validatedAt,
730
+ },
731
+ };
732
+ }
733
+
734
+ function writeAutonomyPolicy(context) {
735
+ const payload = {
736
+ version: 1,
737
+ defaultRiskProfile: "standard",
738
+ levels: {
739
+ green: ["read_files", "run_tests", "update_operational_docs", "write_tmp_files"],
740
+ yellow: ["install_dependencies", "change_structure", "modify_pipeline"],
741
+ red: ["delete_persistent_data", "deploy_to_production", "external_side_effects", "security_changes"],
742
+ },
743
+ approvalRules: {
744
+ destructive_changes_require_approval: true,
745
+ production_deploy_requires_approval: true,
746
+ external_side_effects_require_approval: true,
747
+ },
748
+ };
749
+ writeJson(context.paths.autonomyPolicyFile, payload);
750
+ }
751
+
752
+ function renderGenesis(control, contract) {
753
+ const locale = config.getLocale(control);
754
+ const templatePath = resolveLocalizedFile(TEMPLATES_DIR, locale, "genesis.md");
755
+ let content = fs.readFileSync(templatePath, "utf8");
756
+ const rules = (contract.system.behaviorRules || []).map((item) => `- ${item}`).join("\n") || `- ${t("bootstrap.noneDefined")}`;
757
+ const invariants = (contract.system.architecturalInvariants || []).map((item) => `- ${item}`).join("\n") || `- ${t("bootstrap.noneDefined")}`;
758
+ const services = (contract.system.externalServices || []).length
759
+ ? contract.system.externalServices.map((item) => `| ${item} | ${t("bootstrap.servicePending")} | ${t("bootstrap.servicePending")} |`).join("\n")
760
+ : `| — | — | — |`;
761
+ const pipeline = (contract.execution.pipeline || []).length
762
+ ? contract.execution.pipeline.map((item) => `- ${item}`).join("\n")
763
+ : `- ${t("bootstrap.noneDefined")}`;
764
+ const templates = (contract.execution.templates || []).length
765
+ ? contract.execution.templates.map((item) => `- \`${item}\``).join("\n")
766
+ : `- ${t("bootstrap.noneDefined")}`;
767
+ const schema = JSON.stringify({
768
+ input: {
769
+ source: contract.system.sourceOfTruth,
770
+ schema: contract.system.inputSchema || {},
771
+ },
772
+ output: {
773
+ destination: contract.intent.deliveryTarget,
774
+ schema: contract.system.outputSchema || {},
775
+ },
776
+ }, null, 2);
777
+
778
+ const replacements = {
779
+ PROJECT_NAME: control.meta.projectName || "Project",
780
+ DESIRED_OUTCOME: contract.intent.singularDesiredOutcome || t("bootstrap.pendingValue"),
781
+ SERVICES_TABLE: services,
782
+ SOURCE_OF_TRUTH: contract.system.sourceOfTruth || t("bootstrap.pendingValue"),
783
+ PAYLOAD: contract.intent.deliveryTarget || t("bootstrap.pendingValue"),
784
+ BEHAVIOR_RULES: rules,
785
+ DATA_SCHEMA: schema,
786
+ ARCHITECTURAL_INVARIANTS: invariants,
787
+ PIPELINE_ITEMS: pipeline,
782
788
  TEMPLATE_ITEMS: templates,
783
789
  };
784
790
 
@@ -788,49 +794,49 @@ function renderGenesis(control, contract) {
788
794
  return content;
789
795
  }
790
796
 
791
- function buildSeedTasks(control, profile) {
792
- const phaseOrder = config.getPhases(control);
793
- const firstTaskStatus =
794
- profile.status === "completed"
795
- ? "completed"
796
- : ["blocked", "needs_review"].includes(profile.status)
797
- ? "blocked"
798
- : profile.mode === "agent_handoff"
799
- ? (profile.status === "awaiting_agent" ? "blocked" : "pending")
800
- : "blocked";
801
- const tasks = [
802
- {
803
- id: "opera-bootstrap",
804
- origin: "bootstrap",
805
- title: t("bootstrap.task.bootstrap.title"),
806
- phase: phaseOrder[0]?.id || "O",
807
- stream: "Operations",
808
- priority: "P0",
809
- status: firstTaskStatus,
810
- required: true,
811
- dependsOn: [],
812
- summary: profile.mode === "agent_handoff"
813
- ? t("bootstrap.task.bootstrap.handoffSummary")
814
- : t("bootstrap.task.bootstrap.summary"),
815
- acceptance: profile.mode === "agent_handoff"
816
- ? [
817
- t("bootstrap.acceptance.intake"),
818
- t("bootstrap.acceptance.specDossier"),
819
- t("bootstrap.acceptance.resume"),
820
- ]
821
- : [
822
- t("bootstrap.acceptance.discovery"),
823
- t("bootstrap.acceptance.schema"),
824
- t("bootstrap.acceptance.rules"),
825
- t("bootstrap.acceptance.plan"),
826
- ],
827
- blocker: profile.status === "completed"
828
- ? undefined
829
- : profile.mode === "agent_handoff"
830
- ? t("bootstrap.blocker.awaitingAgent")
831
- : t("bootstrap.blocker.missingData"),
832
- history: [{ at: nowIso(), action: "create", note: t("bootstrap.history.seeded") }],
833
- },
797
+ function buildSeedTasks(control, profile) {
798
+ const phaseOrder = config.getPhases(control);
799
+ const firstTaskStatus =
800
+ profile.status === "completed"
801
+ ? "completed"
802
+ : ["blocked", "needs_review"].includes(profile.status)
803
+ ? "blocked"
804
+ : profile.mode === "agent_handoff"
805
+ ? (profile.status === "awaiting_agent" ? "blocked" : "pending")
806
+ : "blocked";
807
+ const tasks = [
808
+ {
809
+ id: "opera-bootstrap",
810
+ origin: "bootstrap",
811
+ title: t("bootstrap.task.bootstrap.title"),
812
+ phase: phaseOrder[0]?.id || "O",
813
+ stream: "Operations",
814
+ priority: "P0",
815
+ status: firstTaskStatus,
816
+ required: true,
817
+ dependsOn: [],
818
+ summary: profile.mode === "agent_handoff"
819
+ ? t("bootstrap.task.bootstrap.handoffSummary")
820
+ : t("bootstrap.task.bootstrap.summary"),
821
+ acceptance: profile.mode === "agent_handoff"
822
+ ? [
823
+ t("bootstrap.acceptance.intake"),
824
+ t("bootstrap.acceptance.specDossier"),
825
+ t("bootstrap.acceptance.resume"),
826
+ ]
827
+ : [
828
+ t("bootstrap.acceptance.discovery"),
829
+ t("bootstrap.acceptance.schema"),
830
+ t("bootstrap.acceptance.rules"),
831
+ t("bootstrap.acceptance.plan"),
832
+ ],
833
+ blocker: profile.status === "completed"
834
+ ? undefined
835
+ : profile.mode === "agent_handoff"
836
+ ? t("bootstrap.blocker.awaitingAgent")
837
+ : t("bootstrap.blocker.missingData"),
838
+ history: [{ at: nowIso(), action: "create", note: t("bootstrap.history.seeded") }],
839
+ },
834
840
  {
835
841
  id: "opera-prove-integrations",
836
842
  origin: "bootstrap",
@@ -908,229 +914,229 @@ function buildSeedTasks(control, profile) {
908
914
  },
909
915
  ];
910
916
 
911
- return tasks;
912
- }
913
-
914
- function createAwaitingBootstrapState(context) {
915
- return {
916
- version: CONTRACT_VERSION,
917
- mode: null,
918
- status: "awaiting_intake",
919
- routeReason: null,
920
- technicalLevel: null,
921
- projectState: null,
922
- documentationState: null,
923
- decisionOwnership: null,
924
- startedAt: nowIso(),
925
- completedAt: null,
926
- missingFields: [],
927
- discovery: {},
928
- handoffFiles: {
929
- markdown: bootstrapRelativePaths(context).markdown,
930
- json: bootstrapRelativePaths(context).json,
931
- },
932
- intakeFiles: {
933
- json: bootstrapRelativePaths(context).intakeJson,
934
- specDossier: bootstrapRelativePaths(context).specDossier,
935
- },
936
- reviewFiles: {
937
- openQuestions: bootstrapRelativePaths(context).openQuestions,
938
- qualityReport: bootstrapRelativePaths(context).qualityReport,
939
- },
940
- inference: scanProject(context),
941
- };
942
- }
943
-
944
- function applyBootstrap(root, control, profile) {
945
- const context = config.ensureContext(root);
946
- setLocale(config.getLocale(control));
947
- control.meta = control.meta || {};
948
- control.meta.opera = control.meta.opera || {};
949
- control.meta.opera.model = "v3";
950
- writeAutonomyPolicy(context);
951
- control.meta.userProfile = {
952
- technicalLevel: profile.technicalLevel || null,
953
- explanationMode: explanationModeFor(profile.technicalLevel),
954
- capturedAt: nowIso(),
955
- };
956
- control.meta.discovery = {
957
- projectState: profile.projectState || null,
958
- documentationState: profile.documentationState || null,
959
- availableArtifacts: Array.isArray(profile.discovery?.availableArtifacts) ? profile.discovery.availableArtifacts : [],
960
- };
961
- const relative = bootstrapRelativePaths(context);
962
- control.meta.opera.bootstrap = {
963
- ...profile,
964
- handoffFiles: {
965
- markdown: relative.markdown,
966
- json: relative.json,
967
- },
968
- intakeFiles: {
969
- json: relative.intakeJson,
970
- specDossier: relative.specDossier,
971
- },
972
- reviewFiles: {
973
- openQuestions: relative.openQuestions,
974
- qualityReport: relative.qualityReport,
975
- },
976
- };
977
- control.meta.currentFocus = profile.discovery.singularDesiredOutcome || profile.discovery.desiredOutcome || t("bootstrap.defaultFocus");
978
- control.meta.deliveryTarget = profile.discovery.payload || t("bootstrap.defaultTarget");
979
- control.meta.focusPhase = profile.status === "completed" ? "P" : "O";
980
- control.meta.phases = config.getPhases(control);
981
- control.meta.opera.legacyStatus = "supported";
982
-
983
- const remainingTasks = (control.tasks || []).filter((task) => task.id !== "ops-bootstrap" && task.origin !== "bootstrap");
984
- control.tasks = [...remainingTasks, ...buildSeedTasks(control, profile)];
985
-
986
- control.decisionsPending = (profile.missingFields || [])
987
- .filter((field) => !["intakeJson", "specDossier"].includes(field))
988
- .map((field) => ({
989
- owner: "user",
990
- title: t(`bootstrap.field.${field}`),
991
- impact: t("bootstrap.decisionImpact"),
992
- }));
993
- if (profile.mode === "agent_handoff" && profile.status !== "completed") {
994
- control.decisionsPending.unshift({
995
- owner: "user",
996
- title: t("bootstrap.pendingDecision.handoff"),
997
- impact: t("bootstrap.pendingDecision.handoffImpact"),
998
- });
999
- }
1000
-
1001
- control.findings = control.findings || [];
1002
- const files = bootstrapFilePaths(context);
1003
- if (profile.mode === "agent_handoff") {
1004
- writeJson(files.json, buildHandoffPayload(control, profile, context));
1005
- writeText(files.markdown, buildHandoffPrompt(control, profile));
1006
- if (!fs.existsSync(files.specDossier)) {
1007
- writeText(files.specDossier, "# Spec dossier\n\nUse this file to consolidate the project specification before OPERA ingest.\n");
1008
- }
1009
- writeText(files.openQuestions, buildOpenQuestions(["intakeJson", "specDossier"], []));
1010
- }
1011
- const genesisPath = context.paths.genesisFile;
1012
- if (profile.status === "completed") {
1013
- const specText = fs.existsSync(files.specDossier) && readText(files.specDossier).trim()
1014
- ? readText(files.specDossier)
1015
- : `## Problem statement\n${profile.discovery.problemStatement || ""}\n\n## Target user\n${profile.discovery.targetUser || ""}\n\n## Singular desired outcome\n${profile.discovery.singularDesiredOutcome || ""}\n\n## Delivery target\n${profile.discovery.payload || ""}\n\n## Source of truth\n${profile.discovery.sourceOfTruth || ""}\n`;
1016
- const qualityReport = profile.qualityReport || buildQualityReport(context, profile, specText);
1017
- const contract = buildOperatingContract(control, profile, qualityReport, context);
1018
- writeJson(context.paths.contractFile, contract);
1019
- fs.writeFileSync(genesisPath, `${renderGenesis(control, contract)}\n`, "utf8");
1020
- control.meta.opera.contractVersion = CONTRACT_VERSION;
1021
- control.meta.opera.contractReadiness = qualityReport.contractReadiness;
1022
- control.meta.opera.contractFile = contractRelativePath(context);
1023
- control.meta.opera.qualityStatus = qualityReport.status;
1024
- } else if (["needs_review", "blocked"].includes(profile.status)) {
1025
- control.meta.opera.contractVersion = null;
1026
- control.meta.opera.contractReadiness = profile.qualityReport?.contractReadiness || "hypothesis";
1027
- control.meta.opera.qualityStatus = profile.qualityReport?.status || profile.status;
1028
- } else {
1029
- control.meta.opera.contractVersion = null;
1030
- control.meta.opera.contractReadiness = "hypothesis";
1031
- control.meta.opera.qualityStatus = profile.status;
1032
- }
1033
-
1034
- config.saveControl(context, control);
1035
- return control;
1036
- }
1037
-
1038
- function resumeBootstrap(root, control) {
1039
- const context = config.ensureContext(root);
1040
- const bootstrap = getBootstrapState(control, context);
1041
- if (!bootstrap) {
1042
- return { resumed: false, status: "awaiting_intake", reason: "no_bootstrap_state" };
1043
- }
1044
-
1045
- const files = bootstrapFilePaths(context);
1046
- const intake = readJson(files.intakeJson);
1047
- const specDossier = readText(files.specDossier);
1048
- if (!intake || !specDossier.trim()) {
1049
- return { resumed: false, status: "awaiting_agent", reason: "missing_agent_artifacts" };
1050
- }
1051
-
1052
- const discovery = {
1053
- ...bootstrap.discovery,
1054
- ...intake,
1055
- singularDesiredOutcome: intake.singularDesiredOutcome || bootstrap.discovery?.singularDesiredOutcome || "",
1056
- externalServices: normalizeList(intake.externalServices || bootstrap.discovery?.externalServices || []),
1057
- behaviorRules: normalizeList(intake.behaviorRules || bootstrap.discovery?.behaviorRules || []),
1058
- architecturalInvariants: normalizeList(intake.architecturalInvariants || bootstrap.discovery?.architecturalInvariants || []),
1059
- pipeline: normalizeList(intake.pipeline || bootstrap.discovery?.pipeline || []),
1060
- templates: normalizeList(intake.templates || bootstrap.discovery?.templates || []),
1061
- inputSchema: intake.inputSchema || bootstrap.discovery?.inputSchema || {},
1062
- outputSchema: intake.outputSchema || bootstrap.discovery?.outputSchema || {},
1063
- };
1064
- const missingFields = directMissingFields(discovery);
1065
- const profile = {
1066
- ...bootstrap,
1067
- mode: bootstrap.mode || "agent_handoff",
1068
- status: missingFields.length ? "needs_review" : "completed",
1069
- technicalLevel: intake.technicalLevel || bootstrap.technicalLevel || null,
1070
- projectState: intake.projectState || bootstrap.projectState || null,
1071
- documentationState: intake.documentationState || bootstrap.documentationState || null,
1072
- decisionOwnership: intake.decisionOwnership || bootstrap.decisionOwnership || null,
1073
- completedAt: missingFields.length ? null : nowIso(),
1074
- missingFields,
1075
- discovery,
1076
- inference: scanProject(context),
1077
- };
1078
- const qualityReport = buildQualityReport(context, profile, specDossier);
1079
- profile.status = qualityReport.status === "ready" ? "completed" : qualityReport.status;
1080
- profile.completedAt = qualityReport.status === "ready" ? nowIso() : null;
1081
- profile.qualityReport = qualityReport;
1082
- return { resumed: true, profile };
1083
- }
1084
-
1085
- function detectLegacyBootstrap(root, control) {
1086
- const context = config.ensureContext(root);
1087
- if (!config.isOperaInstalled(control)) return null;
1088
- if (control.meta?.opera?.bootstrap) return getBootstrapState(control, context);
1089
- if (control.meta?.opera?.model === "v3") {
1090
- return createAwaitingBootstrapState(context);
1091
- }
1092
- if (!fs.existsSync(context.paths.contractFile)) {
1093
- return {
1094
- version: CONTRACT_VERSION,
1095
- status: "legacy_unsupported",
1096
- mode: null,
1097
- routeReason: "legacy_unsupported",
1098
- technicalLevel: null,
1099
- projectState: null,
1100
- documentationState: null,
1101
- decisionOwnership: null,
1102
- startedAt: control.meta?.opera?.installedAt || nowIso(),
1103
- completedAt: null,
1104
- missingFields: [],
1105
- discovery: {},
1106
- inference: scanProject(context),
1107
- };
1108
- }
1109
- return null;
1110
- }
1111
-
1112
- module.exports = {
1113
- TECHNICAL_LEVELS,
1114
- PROJECT_STATES,
1115
- DOC_STATES,
1116
- DECISION_OWNERSHIPS,
1117
- BOOTSTRAP_MODES,
1118
- QUALITY_STATUSES,
1119
- CONTRACT_READINESS,
1120
- CONTRACT_VERSION,
1121
- bootstrapFilePaths,
1122
- bootstrapRelativePaths,
1123
- contractRelativePath,
1124
- policyRelativePath,
1125
- scanProject,
1126
- collectBootstrapProfile,
1127
- applyBootstrap,
1128
- resumeBootstrap,
1129
- detectLegacyBootstrap,
1130
- getBootstrapState,
1131
- buildQualityReport,
1132
- buildOperatingContract,
1133
- writeAutonomyPolicy,
1134
- createAwaitingBootstrapState,
1135
- isVirginGenesis,
1136
- };
917
+ return tasks;
918
+ }
919
+
920
+ function createAwaitingBootstrapState(context) {
921
+ return {
922
+ version: CONTRACT_VERSION,
923
+ mode: null,
924
+ status: "awaiting_intake",
925
+ routeReason: null,
926
+ technicalLevel: null,
927
+ projectState: null,
928
+ documentationState: null,
929
+ decisionOwnership: null,
930
+ startedAt: nowIso(),
931
+ completedAt: null,
932
+ missingFields: [],
933
+ discovery: {},
934
+ handoffFiles: {
935
+ markdown: bootstrapRelativePaths(context).markdown,
936
+ json: bootstrapRelativePaths(context).json,
937
+ },
938
+ intakeFiles: {
939
+ json: bootstrapRelativePaths(context).intakeJson,
940
+ specDossier: bootstrapRelativePaths(context).specDossier,
941
+ },
942
+ reviewFiles: {
943
+ openQuestions: bootstrapRelativePaths(context).openQuestions,
944
+ qualityReport: bootstrapRelativePaths(context).qualityReport,
945
+ },
946
+ inference: scanProject(context),
947
+ };
948
+ }
949
+
950
+ function applyBootstrap(root, control, profile) {
951
+ const context = config.ensureContext(root);
952
+ setLocale(config.getLocale(control));
953
+ control.meta = control.meta || {};
954
+ control.meta.opera = control.meta.opera || {};
955
+ control.meta.opera.model = "v3";
956
+ writeAutonomyPolicy(context);
957
+ control.meta.userProfile = {
958
+ technicalLevel: profile.technicalLevel || null,
959
+ explanationMode: explanationModeFor(profile.technicalLevel),
960
+ capturedAt: nowIso(),
961
+ };
962
+ control.meta.discovery = {
963
+ projectState: profile.projectState || null,
964
+ documentationState: profile.documentationState || null,
965
+ availableArtifacts: Array.isArray(profile.discovery?.availableArtifacts) ? profile.discovery.availableArtifacts : [],
966
+ };
967
+ const relative = bootstrapRelativePaths(context);
968
+ control.meta.opera.bootstrap = {
969
+ ...profile,
970
+ handoffFiles: {
971
+ markdown: relative.markdown,
972
+ json: relative.json,
973
+ },
974
+ intakeFiles: {
975
+ json: relative.intakeJson,
976
+ specDossier: relative.specDossier,
977
+ },
978
+ reviewFiles: {
979
+ openQuestions: relative.openQuestions,
980
+ qualityReport: relative.qualityReport,
981
+ },
982
+ };
983
+ control.meta.currentFocus = profile.discovery.singularDesiredOutcome || profile.discovery.desiredOutcome || t("bootstrap.defaultFocus");
984
+ control.meta.deliveryTarget = profile.discovery.payload || t("bootstrap.defaultTarget");
985
+ control.meta.focusPhase = profile.status === "completed" ? "P" : "O";
986
+ control.meta.phases = config.getPhases(control);
987
+ control.meta.opera.legacyStatus = "supported";
988
+
989
+ const remainingTasks = (control.tasks || []).filter((task) => task.id !== "ops-bootstrap" && task.origin !== "bootstrap");
990
+ control.tasks = [...remainingTasks, ...buildSeedTasks(control, profile)];
991
+
992
+ control.decisionsPending = (profile.missingFields || [])
993
+ .filter((field) => !["intakeJson", "specDossier"].includes(field))
994
+ .map((field) => ({
995
+ owner: "user",
996
+ title: t(`bootstrap.field.${field}`),
997
+ impact: t("bootstrap.decisionImpact"),
998
+ }));
999
+ if (profile.mode === "agent_handoff" && profile.status !== "completed") {
1000
+ control.decisionsPending.unshift({
1001
+ owner: "user",
1002
+ title: t("bootstrap.pendingDecision.handoff"),
1003
+ impact: t("bootstrap.pendingDecision.handoffImpact"),
1004
+ });
1005
+ }
1006
+
1007
+ control.findings = control.findings || [];
1008
+ const files = bootstrapFilePaths(context);
1009
+ if (profile.mode === "agent_handoff") {
1010
+ writeJson(files.json, buildHandoffPayload(control, profile, context));
1011
+ writeText(files.markdown, buildHandoffPrompt(control, profile));
1012
+ if (!fs.existsSync(files.specDossier)) {
1013
+ writeText(files.specDossier, "# Spec dossier\n\nUse this file to consolidate the project specification before OPERA ingest.\n");
1014
+ }
1015
+ writeText(files.openQuestions, buildOpenQuestions(["intakeJson", "specDossier"], []));
1016
+ }
1017
+ const genesisPath = context.paths.genesisFile;
1018
+ if (profile.status === "completed") {
1019
+ const specText = fs.existsSync(files.specDossier) && readText(files.specDossier).trim()
1020
+ ? readText(files.specDossier)
1021
+ : `## Problem statement\n${profile.discovery.problemStatement || ""}\n\n## Target user\n${profile.discovery.targetUser || ""}\n\n## Singular desired outcome\n${profile.discovery.singularDesiredOutcome || ""}\n\n## Delivery target\n${profile.discovery.payload || ""}\n\n## Source of truth\n${profile.discovery.sourceOfTruth || ""}\n`;
1022
+ const qualityReport = profile.qualityReport || buildQualityReport(context, profile, specText);
1023
+ const contract = buildOperatingContract(control, profile, qualityReport, context);
1024
+ writeJson(context.paths.contractFile, contract);
1025
+ fs.writeFileSync(genesisPath, `${renderGenesis(control, contract)}\n`, "utf8");
1026
+ control.meta.opera.contractVersion = CONTRACT_VERSION;
1027
+ control.meta.opera.contractReadiness = qualityReport.contractReadiness;
1028
+ control.meta.opera.contractFile = contractRelativePath(context);
1029
+ control.meta.opera.qualityStatus = qualityReport.status;
1030
+ } else if (["needs_review", "blocked"].includes(profile.status)) {
1031
+ control.meta.opera.contractVersion = null;
1032
+ control.meta.opera.contractReadiness = profile.qualityReport?.contractReadiness || "hypothesis";
1033
+ control.meta.opera.qualityStatus = profile.qualityReport?.status || profile.status;
1034
+ } else {
1035
+ control.meta.opera.contractVersion = null;
1036
+ control.meta.opera.contractReadiness = "hypothesis";
1037
+ control.meta.opera.qualityStatus = profile.status;
1038
+ }
1039
+
1040
+ config.saveControl(context, control);
1041
+ return control;
1042
+ }
1043
+
1044
+ function resumeBootstrap(root, control) {
1045
+ const context = config.ensureContext(root);
1046
+ const bootstrap = getBootstrapState(control, context);
1047
+ if (!bootstrap) {
1048
+ return { resumed: false, status: "awaiting_intake", reason: "no_bootstrap_state" };
1049
+ }
1050
+
1051
+ const files = bootstrapFilePaths(context);
1052
+ const intake = readJson(files.intakeJson);
1053
+ const specDossier = readText(files.specDossier);
1054
+ if (!intake || !specDossier.trim()) {
1055
+ return { resumed: false, status: "awaiting_agent", reason: "missing_agent_artifacts" };
1056
+ }
1057
+
1058
+ const discovery = {
1059
+ ...bootstrap.discovery,
1060
+ ...intake,
1061
+ singularDesiredOutcome: intake.singularDesiredOutcome || bootstrap.discovery?.singularDesiredOutcome || "",
1062
+ externalServices: normalizeList(intake.externalServices || bootstrap.discovery?.externalServices || []),
1063
+ behaviorRules: normalizeList(intake.behaviorRules || bootstrap.discovery?.behaviorRules || []),
1064
+ architecturalInvariants: normalizeList(intake.architecturalInvariants || bootstrap.discovery?.architecturalInvariants || []),
1065
+ pipeline: normalizeList(intake.pipeline || bootstrap.discovery?.pipeline || []),
1066
+ templates: normalizeList(intake.templates || bootstrap.discovery?.templates || []),
1067
+ inputSchema: intake.inputSchema || bootstrap.discovery?.inputSchema || {},
1068
+ outputSchema: intake.outputSchema || bootstrap.discovery?.outputSchema || {},
1069
+ };
1070
+ const missingFields = directMissingFields(discovery);
1071
+ const profile = {
1072
+ ...bootstrap,
1073
+ mode: bootstrap.mode || "agent_handoff",
1074
+ status: missingFields.length ? "needs_review" : "completed",
1075
+ technicalLevel: intake.technicalLevel || bootstrap.technicalLevel || null,
1076
+ projectState: intake.projectState || bootstrap.projectState || null,
1077
+ documentationState: intake.documentationState || bootstrap.documentationState || null,
1078
+ decisionOwnership: intake.decisionOwnership || bootstrap.decisionOwnership || null,
1079
+ completedAt: missingFields.length ? null : nowIso(),
1080
+ missingFields,
1081
+ discovery,
1082
+ inference: scanProject(context),
1083
+ };
1084
+ const qualityReport = buildQualityReport(context, profile, specDossier);
1085
+ profile.status = qualityReport.status === "ready" ? "completed" : qualityReport.status;
1086
+ profile.completedAt = qualityReport.status === "ready" ? nowIso() : null;
1087
+ profile.qualityReport = qualityReport;
1088
+ return { resumed: true, profile };
1089
+ }
1090
+
1091
+ function detectLegacyBootstrap(root, control) {
1092
+ const context = config.ensureContext(root);
1093
+ if (!config.isOperaInstalled(control)) return null;
1094
+ if (control.meta?.opera?.bootstrap) return getBootstrapState(control, context);
1095
+ if (control.meta?.opera?.model === "v3") {
1096
+ return createAwaitingBootstrapState(context);
1097
+ }
1098
+ if (!fs.existsSync(context.paths.contractFile)) {
1099
+ return {
1100
+ version: CONTRACT_VERSION,
1101
+ status: "legacy_unsupported",
1102
+ mode: null,
1103
+ routeReason: "legacy_unsupported",
1104
+ technicalLevel: null,
1105
+ projectState: null,
1106
+ documentationState: null,
1107
+ decisionOwnership: null,
1108
+ startedAt: control.meta?.opera?.installedAt || nowIso(),
1109
+ completedAt: null,
1110
+ missingFields: [],
1111
+ discovery: {},
1112
+ inference: scanProject(context),
1113
+ };
1114
+ }
1115
+ return null;
1116
+ }
1117
+
1118
+ module.exports = {
1119
+ TECHNICAL_LEVELS,
1120
+ PROJECT_STATES,
1121
+ DOC_STATES,
1122
+ DECISION_OWNERSHIPS,
1123
+ BOOTSTRAP_MODES,
1124
+ QUALITY_STATUSES,
1125
+ CONTRACT_READINESS,
1126
+ CONTRACT_VERSION,
1127
+ bootstrapFilePaths,
1128
+ bootstrapRelativePaths,
1129
+ contractRelativePath,
1130
+ policyRelativePath,
1131
+ scanProject,
1132
+ collectBootstrapProfile,
1133
+ applyBootstrap,
1134
+ resumeBootstrap,
1135
+ detectLegacyBootstrap,
1136
+ getBootstrapState,
1137
+ buildQualityReport,
1138
+ buildOperatingContract,
1139
+ writeAutonomyPolicy,
1140
+ createAwaitingBootstrapState,
1141
+ isVirginGenesis,
1142
+ };