@williambeto/ai-workflow 2.4.9 → 2.6.3

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 (104) hide show
  1. package/bin/ai-workflow.d.ts +3 -0
  2. package/bin/ai-workflow.js +2669 -1
  3. package/bin/ai-workflow.js.map +1 -0
  4. package/chunk-2AOV2ATY.js +749 -0
  5. package/chunk-5WRI5ZAA.js +31 -0
  6. package/chunk-6EMG6FAO.js +734 -0
  7. package/chunk-UHLZX2EG.js +1827 -0
  8. package/core/index.d.ts +440 -0
  9. package/core/index.js +32 -0
  10. package/core/index.js.map +1 -0
  11. package/dist-assets/AGENTS.md +23 -6
  12. package/dist-assets/agents/atlas.md +8 -8
  13. package/dist-assets/agents/nexus.md +4 -4
  14. package/dist-assets/agents/orion.md +1 -1
  15. package/dist-assets/agents/phoenix.md +1 -1
  16. package/dist-assets/agents/sage.md +1 -1
  17. package/dist-assets/commands/implement.md +1 -1
  18. package/dist-assets/docs/QUICKSTART.md +8 -8
  19. package/dist-assets/docs/cli-reference.md +7 -7
  20. package/dist-assets/docs/compatibility/provider-usage.md +1 -9
  21. package/dist-assets/docs/compatibility/runtime-matrix.md +5 -6
  22. package/dist-assets/docs/consumer-onboarding.md +2 -2
  23. package/dist-assets/docs/design-patterns-policy.md +1 -1
  24. package/dist-assets/docs/npm-consumer-quickstart.md +70 -109
  25. package/dist-assets/docs/policies/01-BRANCH_GATE.md +1 -1
  26. package/dist-assets/docs/policies/06-FINAL_EVIDENCE_CONTRACT.md +2 -2
  27. package/dist-assets/docs/policies/08-PRODUCT_TRUTHFULNESS_AND_PROJECT_DOCS.md +1 -1
  28. package/dist-assets/docs/policies/10-BEHAVIORAL_CONTRACT_HARDENING.md +1 -1
  29. package/dist-assets/docs/policies/ORCHESTRATION_PROTOCOL.md +8 -9
  30. package/dist-assets/docs/policies/PROCEDURE_DELIVERY_ARTIFACTS.md +4 -10
  31. package/dist-assets/docs/policies/SKILLS_COMMON_GOVERNANCE.md +1 -1
  32. package/dist-assets/docs/profiles/README.md +2 -2
  33. package/dist-assets/docs/specs/runtime-operational-contract.md +12 -12
  34. package/dist-assets/docs/troubleshooting-guide.md +2 -2
  35. package/dist-assets/docs/visual-validation-guide.md +3 -3
  36. package/dist-assets/schemas/evidence.schema.json +109 -0
  37. package/dist-assets/templates/ANTIGRAVITY.md.template +7 -5
  38. package/dist-assets/templates/CLAUDE.md.template +7 -5
  39. package/dist-assets/templates/CODEX.md.template +7 -5
  40. package/evidence-validator-76ZQQYDU.js +6661 -0
  41. package/package.json +7 -64
  42. package/{src/core/workspace/read-only-workspace.js → read-only-workspace-PZBE7KAX.js} +14 -31
  43. package/{src/commands/validate.js → validate-Q2NLLQ5G.js} +30 -22
  44. package/AGENTS.md +0 -34
  45. package/CHANGELOG.md +0 -259
  46. package/LICENSE +0 -21
  47. package/PUBLISH_MANIFEST.json +0 -34
  48. package/README.md +0 -129
  49. package/dist-assets/templates/.geminiignore.template +0 -8
  50. package/dist-assets/templates/GEMINI.md.template +0 -20
  51. package/docs/compatibility/provider-usage.md +0 -46
  52. package/docs/compatibility/runtime-matrix.md +0 -31
  53. package/docs/getting-started/DESKTOP_PROMPT.md +0 -52
  54. package/docs/getting-started/authoring-custom-skills.md +0 -80
  55. package/docs/getting-started/quickstart.md +0 -117
  56. package/docs/getting-started/troubleshooting.md +0 -123
  57. package/docs/getting-started/upgrading-to-v2.md +0 -62
  58. package/src/adapters/index.js +0 -4
  59. package/src/adapters/platforms/antigravity.js +0 -382
  60. package/src/adapters/platforms/claude.js +0 -126
  61. package/src/adapters/platforms/codex.js +0 -113
  62. package/src/adapters/platforms/gemini.js +0 -245
  63. package/src/cli.js +0 -155
  64. package/src/commands/clean.js +0 -106
  65. package/src/commands/collect-evidence.js +0 -25
  66. package/src/commands/doctor.js +0 -202
  67. package/src/commands/execute.js +0 -382
  68. package/src/commands/init.js +0 -359
  69. package/src/commands/run.js +0 -112
  70. package/src/core/backup.js +0 -65
  71. package/src/core/completion-contract.js +0 -35
  72. package/src/core/delegation-controller.js +0 -193
  73. package/src/core/evidence/evidence-ledger.js +0 -98
  74. package/src/core/execution-planner.js +0 -62
  75. package/src/core/filesystem.js +0 -141
  76. package/src/core/finalization/finalizer.js +0 -165
  77. package/src/core/finalization/workspace-snapshot.js +0 -110
  78. package/src/core/gates/branch-gate.js +0 -134
  79. package/src/core/gates/merge-gate.js +0 -74
  80. package/src/core/handoff/handoff-engine.js +0 -104
  81. package/src/core/healing/cli-remediation-executor.js +0 -153
  82. package/src/core/healing/healer-engine.js +0 -212
  83. package/src/core/healing/runtime-remediation-executor.js +0 -136
  84. package/src/core/identity.js +0 -43
  85. package/src/core/install-plan.js +0 -34
  86. package/src/core/opencode-merge.js +0 -150
  87. package/src/core/package-assets.js +0 -141
  88. package/src/core/request-classifier.js +0 -73
  89. package/src/core/request-router.js +0 -289
  90. package/src/core/runtime/opencode-adapter.js +0 -202
  91. package/src/core/sdd/validator.js +0 -67
  92. package/src/core/statuses.js +0 -29
  93. package/src/core/symlink-layout.js +0 -93
  94. package/src/core/templates.js +0 -229
  95. package/src/core/validation/artifact-fidelity-gate.js +0 -445
  96. package/src/core/validation/canonical-finalization.js +0 -43
  97. package/src/core/validation/delivery-decision-engine.js +0 -344
  98. package/src/core/validation/evidence-collector.js +0 -245
  99. package/src/core/validation/quality-guard.js +0 -251
  100. package/src/core/validation/stack-detector.js +0 -65
  101. package/src/core/validation/validation-planner.js +0 -134
  102. package/src/core/validation/visual-verifier.js +0 -175
  103. package/src/core/workflow-profiles.js +0 -107
  104. package/src/core/workflow-state-machine.js +0 -46
@@ -1,359 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { createInstallPlan } from "../core/install-plan.js";
4
- import { buildAiWorkflowConfig, getTemplateFiles, isValidProfile } from "../core/templates.js";
5
- import { exists, writeFileSafe, readJson } from "../core/filesystem.js";
6
- import { createManagedBackup, createManagedPathBackup } from "../core/backup.js";
7
- import { mergeOpencodeConfig } from "../core/opencode-merge.js";
8
- import { buildSymlinkEntries, isSymlinkTo, setupInternalSymlinks } from "../core/symlink-layout.js";
9
- import { GeminiAdapter, ClaudeAdapter, CodexAdapter, AntigravityAdapter } from "../adapters/index.js";
10
-
11
- function summarize(actions) {
12
- const createCount = actions.filter((a) => a.type === "create").length;
13
- const conflictCount = actions.filter((a) => a.type === "conflict").length;
14
-
15
- return { createCount, conflictCount };
16
- }
17
-
18
- function printPlan(actions) {
19
- for (const action of actions) {
20
- console.log(`- ${action.type.padEnd(8)} ${action.relativePath}`);
21
- }
22
- }
23
-
24
- function toIgnoreEntries(linkPath) {
25
- if (linkPath === "README.workflow.md") {
26
- return [linkPath];
27
- }
28
-
29
- const normalized = linkPath.endsWith("/") ? linkPath.slice(0, -1) : linkPath;
30
- return [normalized, `${normalized}/`];
31
- }
32
-
33
- function buildGitignoreBlock(entries) {
34
- const unique = Array.from(new Set(entries));
35
- const sorted = unique.sort((a, b) => a.localeCompare(b));
36
-
37
- return [
38
- "# BEGIN AI WORKFLOW KIT",
39
- "# AI Workflow Kit generated files",
40
- ...sorted,
41
- "# END AI WORKFLOW KIT"
42
- ].join("\n");
43
- }
44
-
45
- async function upsertGitignoreBlock(cwd, entries) {
46
- const gitignorePath = path.join(cwd, ".gitignore");
47
- const block = buildGitignoreBlock(entries);
48
- const beginMarker = "# BEGIN AI WORKFLOW KIT";
49
- const endMarker = "# END AI WORKFLOW KIT";
50
-
51
- const hasGitignore = await exists(gitignorePath);
52
- if (!hasGitignore) {
53
- await writeFileSafe(gitignorePath, `${block}\n`);
54
- return "created";
55
- }
56
-
57
- const current = await fs.readFile(gitignorePath, "utf8");
58
- const beginIndex = current.indexOf(beginMarker);
59
- const endIndex = current.indexOf(endMarker);
60
-
61
- if (beginIndex >= 0 && endIndex > beginIndex) {
62
- const before = current
63
- .slice(0, beginIndex)
64
- .replace(/\n?# AI Workflow Kit generated files\n?$/, "\n")
65
- .replace(/[ \t]+$/gm, "")
66
- .replace(/\n*$/, "\n");
67
- const afterStart = endIndex + endMarker.length;
68
- const after = current.slice(afterStart).replace(/^\n*/, "\n");
69
- const next = `${before}${block}${after}`;
70
- if (next !== current) {
71
- await writeFileSafe(gitignorePath, next);
72
- return "updated";
73
- }
74
- return "unchanged";
75
- }
76
-
77
- const separator = current.endsWith("\n") ? "\n" : "\n\n";
78
- const next = `${current}${separator}${block}\n`;
79
- await writeFileSafe(gitignorePath, next);
80
- return "updated";
81
- }
82
-
83
- async function injectScripts(cwd) {
84
- const pkgPath = path.join(cwd, "package.json");
85
- if (!(await exists(pkgPath))) return;
86
-
87
- try {
88
- const pkg = await readJson(pkgPath);
89
- if (!pkg.scripts) pkg.scripts = {};
90
-
91
- if (!pkg.scripts.validate) {
92
- pkg.scripts.validate = "ai-workflow collect-evidence";
93
- await writeFileSafe(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
94
- return true;
95
- }
96
- } catch {
97
- // Ignore invalid package.json
98
- }
99
- return false;
100
- }
101
-
102
- export async function runInit({
103
- cwd, yes, force, dryRun, noInstall, noOverwrite, gemini, claude, codex, antigravity, "dev-mode": devMode, profile }) {
104
- // Self-execution guard
105
- if (!devMode) {
106
- try {
107
- const pkgPath = path.join(cwd, "package.json");
108
- if (await exists(pkgPath)) {
109
- const pkg = await readJson(pkgPath);
110
- if (pkg.name === "@williambeto/ai-workflow") {
111
- console.error("\nFATAL: Cannot run 'ai-workflow init' inside its own development repository.");
112
- console.error("This would corrupt the development environment. Aborting.");
113
- console.error("Use the --dev-mode flag if you are developing the kit itself and need to test the init command.\n");
114
- process.exit(1);
115
- }
116
- }
117
- } catch {
118
- // Ignore if package.json is unreadable. The guard is a safety net.
119
- }
120
- }
121
-
122
- const selectedProfile = profile || "standard";
123
-
124
- if (!isValidProfile(selectedProfile)) {
125
- console.error(`Invalid profile: "${selectedProfile}". Valid profiles: standard, full`);
126
- process.exit(1);
127
- }
128
- const installRoot = ".ai-workflow";
129
-
130
- const platforms = [];
131
- if (gemini) platforms.push("gemini");
132
- if (claude) platforms.push("claude");
133
- if (codex) platforms.push("codex");
134
- if (antigravity) platforms.push("antigravity");
135
-
136
- console.log(`ai-workflow: initializing in ${cwd}`);
137
- console.log(`Profile: ${selectedProfile}`);
138
- if (platforms.length > 0) {
139
- console.log(`Platforms: ${platforms.join(", ")}`);
140
- }
141
-
142
- const actions = await createInstallPlan({ cwd, exists, profile: selectedProfile });
143
- const linkEntries = buildSymlinkEntries({ templateFiles: getTemplateFiles(selectedProfile), installRoot });
144
- const { createCount, conflictCount } = summarize(actions);
145
-
146
- console.log(`Plan: ${createCount} create, ${conflictCount} conflict`);
147
- printPlan(actions);
148
-
149
- if (!yes && conflictCount > 0 && !force && !noOverwrite) {
150
- throw new Error(
151
- "conflicts detected. Re-run with --force to replace managed files or resolve conflicts manually"
152
- );
153
- }
154
-
155
- if (dryRun) {
156
- console.log("Dry-run enabled. No files were written.");
157
- if (platforms.length > 0) {
158
- console.log("ai-workflow: (dry-run) platform migration would be executed after file writing.");
159
- }
160
- return;
161
- }
162
-
163
- const skipped = [];
164
- const backups = [];
165
- const backupRoot = ".ai-workflow-backups";
166
-
167
- for (const action of actions) {
168
- if (action.type === "conflict" && (noOverwrite || !force)) {
169
- skipped.push(action.relativePath);
170
- continue;
171
- }
172
-
173
- if (action.type === "conflict" && force) {
174
- const backupPath = await createManagedBackup(action.absolutePath, {
175
- cwd,
176
- backupRoot,
177
- maxPerFile: 20
178
- });
179
- backups.push(backupPath);
180
- }
181
-
182
- const content =
183
- action.relativePath === ".ai-workflow/config.json"
184
- ? `${JSON.stringify(
185
- buildAiWorkflowConfig({
186
- profile: selectedProfile
187
- }),
188
- null,
189
- 2
190
- )}\n`
191
- : action.content;
192
-
193
- await writeFileSafe(action.absolutePath, content);
194
- }
195
-
196
- const linkBackups = [];
197
- const linkCreated = [];
198
-
199
- for (const { linkPath, targetPath } of linkEntries) {
200
- const absoluteLinkPath = path.join(cwd, linkPath);
201
- const absoluteTargetPath = path.join(cwd, targetPath);
202
-
203
- if (!(await exists(absoluteTargetPath))) {
204
- throw new Error(`missing link target: ${targetPath}`);
205
- }
206
-
207
- if (await isSymlinkTo(absoluteLinkPath, absoluteTargetPath)) {
208
- continue;
209
- }
210
-
211
- if (await exists(absoluteLinkPath)) {
212
- if (!force) {
213
- throw new Error(
214
- `cannot create symlink '${linkPath}' because path already exists. Re-run with --force to backup and replace. If symlink creation fails, see troubleshooting in docs/npm-consumer-quickstart.md.`
215
- );
216
- }
217
- const backupPath = await createManagedPathBackup(absoluteLinkPath, {
218
- cwd,
219
- backupRoot,
220
- maxPerFile: 20
221
- });
222
- linkBackups.push(backupPath);
223
- // Ensure we remove the old path before symlinking if it's not already handled by backup
224
- await fs.rm(absoluteLinkPath, { recursive: true, force: true });
225
- }
226
-
227
- const stat = await fs.lstat(absoluteTargetPath);
228
- const type = process.platform === "win32" ? (stat.isDirectory() ? "junction" : "file") : (stat.isDirectory() ? "dir" : "file");
229
- const linkTarget = process.platform === "win32" ? absoluteTargetPath : path.relative(path.dirname(absoluteLinkPath), absoluteTargetPath);
230
-
231
- try {
232
- await fs.mkdir(path.dirname(absoluteLinkPath), { recursive: true });
233
- await fs.symlink(linkTarget, absoluteLinkPath, type);
234
- } catch (error) {
235
- throw new Error(
236
- `failed to create symlink '${linkPath}' -> '${targetPath}' (${error.message}). ` +
237
- "For Windows, enable Developer Mode or run terminal as Administrator. " +
238
- "For Linux/macOS, verify write permissions in project root."
239
- );
240
- }
241
- linkCreated.push(linkPath);
242
- }
243
-
244
- await setupInternalSymlinks(cwd, installRoot);
245
-
246
- const opencodeResult = await mergeOpencodeConfig(cwd, { force, backupRoot });
247
-
248
- const generatedIgnoreEntries = [
249
- ".ai-workflow/",
250
- ".ai-workflow-backups/",
251
- "opencode.jsonc.backup.*",
252
- ...linkEntries.flatMap((entry) => toIgnoreEntries(entry.linkPath))
253
- ];
254
-
255
- if (gemini) {
256
- generatedIgnoreEntries.push(".gemini/");
257
- }
258
- if (codex) {
259
- generatedIgnoreEntries.push(".agents/", ".codex/");
260
- }
261
- if (claude) {
262
- generatedIgnoreEntries.push(".claude/");
263
- }
264
- if (antigravity) {
265
- generatedIgnoreEntries.push(".agents/");
266
- }
267
-
268
- const gitignoreResult = await upsertGitignoreBlock(cwd, generatedIgnoreEntries);
269
- const scriptInjected = await injectScripts(cwd);
270
-
271
- if (platforms.length > 0) {
272
- console.log("ai-workflow: migrating to native platforms...");
273
- const agentsDir = path.join(cwd, installRoot, "opencode/agents");
274
- const skillsDir = path.join(cwd, installRoot, "opencode/skills");
275
-
276
- const agentFiles = (await fs.readdir(agentsDir).catch(() => []))
277
- .filter((f) => f.endsWith(".md"))
278
- .map((f) => path.join(agentsDir, f));
279
-
280
- if (gemini) {
281
- const adapter = new GeminiAdapter({ cwd });
282
-
283
- // Collect specialized skills from the new opencode/skills path
284
- const skillFolders = await fs.readdir(skillsDir).catch(() => []);
285
- const skillFiles = [];
286
- for (const folder of skillFolders) {
287
- const skillPath = path.join(skillsDir, folder, "SKILL.md");
288
- if (await exists(skillPath)) {
289
- skillFiles.push(skillPath);
290
- }
291
- }
292
-
293
- const allFiles = [...agentFiles, ...skillFiles];
294
- const transformed = await Promise.all(allFiles.map((f) => adapter.transform(f)));
295
- await adapter.deploy(transformed, installRoot);
296
- console.log(`- gemini: deployed AI Workflow Kit agents to .gemini/ (native discovery enabled)`);
297
- }
298
-
299
- if (claude) {
300
- const adapter = new ClaudeAdapter({ cwd });
301
- await adapter.deploy(installRoot);
302
- console.log("- claude: deployed AI Workflow Kit agents to CLAUDE.md and .claude/rules/ (native discovery enabled)");
303
- }
304
-
305
- if (codex) {
306
- const adapter = new CodexAdapter({ cwd });
307
- await adapter.deploy(installRoot);
308
- console.log("- codex: deployed AI Workflow Kit agents to .agents/, .codex/, and .github/ (native discovery enabled)");
309
- }
310
-
311
- if (antigravity) {
312
- const adapter = new AntigravityAdapter({ cwd });
313
-
314
- // Collect specialized skills from the new opencode/skills path
315
- const skillFolders = await fs.readdir(skillsDir).catch(() => []);
316
- const skillFiles = [];
317
- for (const folder of skillFolders) {
318
- const skillPath = path.join(skillsDir, folder, "SKILL.md");
319
- if (await exists(skillPath)) {
320
- skillFiles.push(skillPath);
321
- }
322
- }
323
-
324
- const allFiles = [...agentFiles, ...skillFiles];
325
- const transformed = await Promise.all(allFiles.map((f) => adapter.transform(f)));
326
- await adapter.deploy(transformed, installRoot);
327
- console.log(`- antigravity: deployed AI Workflow Kit agents to .agents/ (native discovery enabled)`);
328
- }
329
- }
330
-
331
- console.log("Installation complete.");
332
- console.log(`- profile: ${selectedProfile}`);
333
-
334
- console.log(`- opencode.jsonc: ${opencodeResult.reason}`);
335
- console.log(`- .gitignore: ${gitignoreResult}`);
336
- console.log(`- scripts: ${scriptInjected ? "injected 'validate'" : "already-up-to-date"}`);
337
- console.log(`- symlink layout: ${linkCreated.length > 0 ? `created ${linkCreated.length}` : "already-up-to-date"}`);
338
- if (backups.length > 0) {
339
- console.log(`- backups created: ${backups.length}`);
340
- for (const filePath of backups) {
341
- console.log(` ${filePath}`);
342
- }
343
- }
344
- if (linkBackups.length > 0) {
345
- console.log(`- symlink backups created: ${linkBackups.length}`);
346
- for (const filePath of linkBackups) {
347
- console.log(` ${filePath}`);
348
- }
349
- }
350
- if (skipped.length > 0) {
351
- console.log(`- conflicts skipped: ${skipped.length}`);
352
- for (const relativePath of skipped) {
353
- console.log(` ${relativePath}`);
354
- }
355
- }
356
- console.log(`- dependency install: ${noInstall ? "skipped (--no-install)" : "not managed in this step"}`);
357
- console.log("Next steps:");
358
- console.log(" npx @williambeto/ai-workflow doctor");
359
- }
@@ -1,112 +0,0 @@
1
- import { BranchGate } from "../core/gates/branch-gate.js";
2
- import { SpecValidator } from "../core/sdd/validator.js";
3
- import { HandoffEngine } from "../core/handoff/handoff-engine.js";
4
- import { runCollectEvidence } from "./collect-evidence.js";
5
- import { QualityGuard } from "../core/validation/quality-guard.js";
6
- import { HealerEngine } from "../core/healing/healer-engine.js";
7
- import { createCliRemediationExecutor } from "../core/healing/cli-remediation-executor.js";
8
- import { isRecoverableGateFailure, isTerminalFailure } from "../core/statuses.js";
9
- import path from "node:path";
10
- import fs from "node:fs/promises";
11
- import { getWorkflowProfile, resolveWorkflowProfile } from "../core/workflow-profiles.js";
12
-
13
- function normalizeTaskSlugFromSpec(specPath) {
14
- const normalized = String(specPath).replaceAll("\\", "/");
15
- const marker = "/docs/workflows/";
16
- const index = normalized.includes(marker)
17
- ? normalized.indexOf(marker) + marker.length
18
- : normalized.startsWith("docs/workflows/") ? "docs/workflows/".length : -1;
19
- if (index >= 0) {
20
- const slug = normalized.slice(index).split("/")[0];
21
- if (slug) return slug;
22
- }
23
- return path.basename(specPath, path.extname(specPath));
24
- }
25
-
26
- function combineValidation(evidence, quality) {
27
- const statuses = [evidence.internalStatus, quality.overallStatus];
28
- let overallStatus = "PASS";
29
- if (statuses.includes("BLOCKED")) overallStatus = "BLOCKED";
30
- else if (statuses.includes("FAIL")) overallStatus = "FAIL";
31
- else if (statuses.includes("FAIL_DELEGATION_GATE")) overallStatus = "FAIL_DELEGATION_GATE";
32
- else if (statuses.includes("FAIL_QUALITY_GATE")) overallStatus = "FAIL_QUALITY_GATE";
33
- else if (statuses.includes("PASS_WITH_NOTES")) overallStatus = "PASS_WITH_NOTES";
34
- return { overallStatus, evidence, quality };
35
- }
36
-
37
- /**
38
- * Coordinates branch safety, specification validation, observed validation, bounded remediation, and handoff.
39
- * Failed required validation can never be promoted to success.
40
- */
41
- export async function runMasterOrchestrator({ cwd, specPath, override, remediationExecutor = null }) {
42
- console.log("\n[AI WORKFLOW] Starting evidence-backed orchestration...\n");
43
-
44
- const branchGate = new BranchGate({ memoryDir: path.join(cwd, ".ai-workflow"), cwd });
45
- const taskSlug = specPath ? normalizeTaskSlugFromSpec(specPath) : "implementation";
46
- const gateResult = branchGate.check(override, { autoRecover: true, taskSlug });
47
- if (gateResult.blocked) throw new Error(`[GATE BLOCKED] ${gateResult.reason}`);
48
- console.log(`[PASS] Branch Gate: ${gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : `${gateResult.branch} is authorized`}.`);
49
-
50
- if (!specPath) throw new Error("[SDD BLOCKED] Missing --spec-path. An approved specification is required.");
51
-
52
- const validator = new SpecValidator();
53
- const absoluteSpecPath = path.isAbsolute(specPath) ? specPath : path.join(cwd, specPath);
54
- const validation = await validator.validate(absoluteSpecPath);
55
- if (!validation.valid) throw new Error(`[SDD BLOCKED] Specification is not ready: ${validation.reason}`);
56
-
57
- const executionMode = validation.tier === "deep" ? "full" : validation.tier === "tiny" ? "quick" : "standard";
58
- const specContent = await fs.readFile(absoluteSpecPath, "utf8");
59
- const workflowProfile = resolveWorkflowProfile({ request: specContent });
60
- const profileDefinition = getWorkflowProfile(workflowProfile);
61
- console.log(`[PASS] Specification: ${validation.tier.toUpperCase()} spec is APPROVED.`);
62
- console.log(`[PROFILE] ${workflowProfile} -> ${profileDefinition.owner}; skills: ${profileDefinition.skills.join(", ") || "none"}.`);
63
-
64
- const validateWorkflow = async () => {
65
- const evidence = await runCollectEvidence({
66
- cwd,
67
- exitOnError: false,
68
- taskSlug,
69
- mode: executionMode,
70
- profile: workflowProfile,
71
- branchRecovery: gateResult.recovered ? `${gateResult.branchBefore} -> ${gateResult.branch}` : "NOT_REQUIRED"
72
- });
73
- const quality = await new QualityGuard({ cwd, taskSlug, mode: executionMode }).verify();
74
- return combineValidation(evidence, quality);
75
- };
76
-
77
- let result = await validateWorkflow();
78
- if (isRecoverableGateFailure(result.overallStatus)) {
79
- console.log(`\n[REMEDIATION REQUIRED] ${result.overallStatus}. Starting bounded ${executionMode} remediation.`);
80
- // Use the provided executor (programmatic API) or fall back to the default CLI executor.
81
- // The CLI executor re-runs failed npm scripts; it does not modify source code.
82
- const executor = typeof remediationExecutor === "function"
83
- ? remediationExecutor
84
- : createCliRemediationExecutor(cwd);
85
- const healer = new HealerEngine({ cwd, mode: executionMode, taskSlug });
86
- result = await healer.run({ initialResult: result, validate: validateWorkflow, remediate: executor });
87
- }
88
-
89
- if (isTerminalFailure(result.overallStatus)) {
90
- throw new Error(`[WORKFLOW BLOCKED] ${result.overallStatus}: ${result.reason || "Validation could not be resolved safely."}`);
91
- }
92
- if (isRecoverableGateFailure(result.overallStatus)) {
93
- throw new Error(`[WORKFLOW BLOCKED] Unresolved gate status after remediation: ${result.overallStatus}`);
94
- }
95
-
96
- console.log("\n--- Final Handoff ---");
97
- const handoffEngine = new HandoffEngine({ cwd });
98
- const handoffPath = await handoffEngine.generate({
99
- taskId: path.basename(specPath, ".md"),
100
- status: result.overallStatus,
101
- specPaths: [specPath],
102
- evidence: result.evidence,
103
- nextActions: `Profile ${workflowProfile}. Observed validation completed. Ready for review or explicitly approved release actions.`
104
- });
105
-
106
- console.log(`\n[AI WORKFLOW COMPLETE] ${result.overallStatus}`);
107
- if (result.remediation?.attempts) console.log(`Remediation attempts: ${result.remediation.attempts}`);
108
- console.log(`Handoff Packet: ${path.relative(cwd, handoffPath)}\n`);
109
- return { ...result, workflowProfile };
110
- }
111
-
112
- export { combineValidation };
@@ -1,65 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
-
4
- export function buildBackupPath(filePath, now = new Date()) {
5
- const stamp = now.toISOString().replace(/[:.]/g, "-");
6
- return `${filePath}.bak.${stamp}`;
7
- }
8
-
9
- export async function createBackup(filePath) {
10
- const backupPath = buildBackupPath(filePath);
11
- await fs.copyFile(filePath, backupPath);
12
- return backupPath;
13
- }
14
-
15
- function escapeRelativePath(relativePath) {
16
- return relativePath.replace(/[\\/]/g, "__");
17
- }
18
-
19
- export async function createManagedBackup(filePath, { cwd, backupRoot, maxPerFile = 20 }) {
20
- const relativePath = path.relative(cwd, filePath);
21
- const stamp = new Date().toISOString().replace(/[:.]/g, "-");
22
- const backupName = `${escapeRelativePath(relativePath)}.${stamp}.bak`;
23
- const absoluteBackupRoot = path.join(cwd, backupRoot);
24
- const backupPath = path.join(absoluteBackupRoot, backupName);
25
-
26
- await fs.mkdir(absoluteBackupRoot, { recursive: true });
27
- await fs.copyFile(filePath, backupPath);
28
-
29
- const prefix = `${escapeRelativePath(relativePath)}.`;
30
- const entries = await fs.readdir(absoluteBackupRoot);
31
- const matching = entries
32
- .filter((entry) => entry.startsWith(prefix) && entry.endsWith(".bak"))
33
- .sort();
34
-
35
- if (matching.length > maxPerFile) {
36
- const excess = matching.slice(0, matching.length - maxPerFile);
37
- await Promise.all(excess.map((entry) => fs.unlink(path.join(absoluteBackupRoot, entry))));
38
- }
39
-
40
- return backupPath;
41
- }
42
-
43
- export async function createManagedPathBackup(targetPath, { cwd, backupRoot, maxPerFile = 20 }) {
44
- const relativePath = path.relative(cwd, targetPath);
45
- const stamp = new Date().toISOString().replace(/[:.]/g, "-");
46
- const backupName = `${escapeRelativePath(relativePath)}.${stamp}.bak`;
47
- const absoluteBackupRoot = path.join(cwd, backupRoot);
48
- const backupPath = path.join(absoluteBackupRoot, backupName);
49
-
50
- await fs.mkdir(absoluteBackupRoot, { recursive: true });
51
- await fs.rename(targetPath, backupPath);
52
-
53
- const prefix = `${escapeRelativePath(relativePath)}.`;
54
- const entries = await fs.readdir(absoluteBackupRoot);
55
- const matching = entries
56
- .filter((entry) => entry.startsWith(prefix) && entry.endsWith(".bak"))
57
- .sort();
58
-
59
- if (matching.length > maxPerFile) {
60
- const excess = matching.slice(0, matching.length - maxPerFile);
61
- await Promise.all(excess.map((entry) => fs.rm(path.join(absoluteBackupRoot, entry), { recursive: true, force: true })));
62
- }
63
-
64
- return backupPath;
65
- }
@@ -1,35 +0,0 @@
1
- /**
2
- * Evaluates objective owner-output completeness. It does not judge visual taste.
3
- * A caller may resume the same owner once; a second incomplete result must block.
4
- */
5
- export function evaluateOwnerCompletion({
6
- expectedFiles = [],
7
- existingFiles = [],
8
- validation = {},
9
- ownerReportComplete = false,
10
- resumeCount = 0
11
- } = {}) {
12
- const existing = new Set(existingFiles);
13
- const missingFiles = expectedFiles.filter((file) => !existing.has(file));
14
- const missingValidation = Object.entries(validation)
15
- .filter(([, value]) => value === "REQUIRED" || value === false)
16
- .map(([key]) => key);
17
- const missing = [
18
- ...missingFiles.map((file) => `file:${file}`),
19
- ...missingValidation.map((check) => `validation:${check}`),
20
- ...(!ownerReportComplete ? ["owner-report"] : [])
21
- ];
22
-
23
- if (missing.length === 0) {
24
- return { status: "PASS", action: "continue", missing: [] };
25
- }
26
- if (resumeCount < 1) {
27
- return { status: "INCOMPLETE", action: "resume-same-owner", missing };
28
- }
29
- return {
30
- status: "BLOCKED",
31
- action: "stop",
32
- reason: "Owner output remained incomplete after one resume",
33
- missing
34
- };
35
- }