@wizdear/atlas-code 0.2.5 → 0.2.6

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 (64) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-factory.d.ts +2 -4
  3. package/dist/agent-factory.d.ts.map +1 -1
  4. package/dist/agent-factory.js +9 -12
  5. package/dist/agent-factory.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +64 -16
  8. package/dist/cli.js.map +1 -1
  9. package/dist/discovery.d.ts +0 -2
  10. package/dist/discovery.d.ts.map +1 -1
  11. package/dist/discovery.js +0 -1
  12. package/dist/discovery.js.map +1 -1
  13. package/dist/extension.d.ts.map +1 -1
  14. package/dist/extension.js +23 -64
  15. package/dist/extension.js.map +1 -1
  16. package/dist/index.d.ts +1 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/orchestrator.d.ts +0 -2
  21. package/dist/orchestrator.d.ts.map +1 -1
  22. package/dist/orchestrator.js +0 -1
  23. package/dist/orchestrator.js.map +1 -1
  24. package/dist/pipeline.d.ts +0 -5
  25. package/dist/pipeline.d.ts.map +1 -1
  26. package/dist/pipeline.js +0 -3
  27. package/dist/pipeline.js.map +1 -1
  28. package/dist/planner.d.ts +0 -2
  29. package/dist/planner.d.ts.map +1 -1
  30. package/dist/planner.js +0 -6
  31. package/dist/planner.js.map +1 -1
  32. package/dist/roles/cicd.d.ts +1 -1
  33. package/dist/roles/cicd.d.ts.map +1 -1
  34. package/dist/roles/cicd.js +5 -0
  35. package/dist/roles/cicd.js.map +1 -1
  36. package/dist/roles/reviewer.d.ts +1 -1
  37. package/dist/roles/reviewer.d.ts.map +1 -1
  38. package/dist/roles/reviewer.js +7 -1
  39. package/dist/roles/reviewer.js.map +1 -1
  40. package/dist/roles/standards-enricher.d.ts +1 -1
  41. package/dist/roles/standards-enricher.d.ts.map +1 -1
  42. package/dist/roles/standards-enricher.js +8 -0
  43. package/dist/roles/standards-enricher.js.map +1 -1
  44. package/dist/roles/tester.d.ts +1 -1
  45. package/dist/roles/tester.d.ts.map +1 -1
  46. package/dist/roles/tester.js +7 -0
  47. package/dist/roles/tester.js.map +1 -1
  48. package/dist/standards.d.ts +37 -11
  49. package/dist/standards.d.ts.map +1 -1
  50. package/dist/standards.js +71 -90
  51. package/dist/standards.js.map +1 -1
  52. package/dist/step-executor.d.ts +13 -2
  53. package/dist/step-executor.d.ts.map +1 -1
  54. package/dist/step-executor.js +125 -29
  55. package/dist/step-executor.js.map +1 -1
  56. package/dist/store.d.ts +0 -10
  57. package/dist/store.d.ts.map +1 -1
  58. package/dist/store.js +0 -41
  59. package/dist/store.js.map +1 -1
  60. package/dist/system-architect.d.ts +0 -2
  61. package/dist/system-architect.d.ts.map +1 -1
  62. package/dist/system-architect.js +0 -6
  63. package/dist/system-architect.js.map +1 -1
  64. package/package.json +1 -1
@@ -1,3 +1,5 @@
1
+ import { access, readFile } from "node:fs/promises";
2
+ import { join } from "node:path";
1
3
  import { createRoleAgent, filterSerializableMessages, runAgentWithHistory } from "./agent-factory.js";
2
4
  import { createModuleLogger, noopLogger } from "./logger.js";
3
5
  import { getSystemPromptForRole } from "./roles/index.js";
@@ -179,28 +181,77 @@ function extractFromCodeFence(text, artifactName) {
179
181
  function isArtifactInfoString(infoString) {
180
182
  return infoString.includes(".") || infoString.includes("/");
181
183
  }
182
- // ─── Skill Search Instructions ───────────────────────────────────────────────
184
+ /** Checks whether a file exists at the given path. */
185
+ async function fileExistsAt(path) {
186
+ try {
187
+ await access(path);
188
+ return true;
189
+ }
190
+ catch {
191
+ return false;
192
+ }
193
+ }
183
194
  /**
184
- * Instructions for searching past artifacts to add to specific actions when availableSkills is present.
185
- * The "past feature artifacts" keyword matches the qmd-memory description in Available Skills.
195
+ * Detects test command and CI checks from project files when config.testing is empty.
196
+ * This is a fast file-existence check NOT a full project-analyze re-run.
197
+ * Used for new projects where project-analyze ran before the project scaffold existed.
186
198
  */
187
- const SKILL_SEARCH_INSTRUCTIONS = {
188
- plan: "Before planning, search past feature artifacts for similar features, design patterns, or recurring decisions.",
189
- design: "Before designing, search past feature artifacts for related design patterns and architectural decisions.",
190
- test: "Search past feature artifacts for related test patterns, integration points, and previously discovered defects before writing tests.",
191
- regression: "Search past feature artifacts for areas previously affected by regressions before running regression tests.",
192
- review: "Search past feature artifacts for recurring review feedback and common issues before reviewing.",
193
- investigate: "Search past feature artifacts for similar issues and prior investigation findings.",
194
- diagnose: "Search past feature artifacts for similar bug patterns and fix strategies.",
195
- implement: "Search past feature artifacts for similar implementation patterns, code conventions, and lessons learned before implementing.",
196
- fix: "Search past feature artifacts for similar fixes, related code areas, and previously applied fix strategies.",
197
- analyze: "Search past feature artifacts for prior impact analyses, affected areas, and risk patterns before analyzing.",
198
- };
199
+ export async function detectTestConfig(projectRoot) {
200
+ const result = { ciChecks: [] };
201
+ // 1. Read package.json scripts.test
202
+ try {
203
+ const raw = await readFile(join(projectRoot, "package.json"), "utf-8");
204
+ const pkg = JSON.parse(raw);
205
+ const testScript = pkg.scripts?.test;
206
+ if (testScript && testScript !== 'echo "Error: no test specified" && exit 1') {
207
+ result.testCommand = testScript;
208
+ }
209
+ }
210
+ catch {
211
+ /* no package.json */
212
+ }
213
+ // 2. Check for vitest/jest config → fallback test command
214
+ if (!result.testCommand) {
215
+ const testConfigs = [
216
+ { file: "vitest.config.ts", command: "npx vitest run" },
217
+ { file: "vitest.config.js", command: "npx vitest run" },
218
+ { file: "vite.config.ts", command: "npx vitest run" },
219
+ { file: "jest.config.ts", command: "npx jest" },
220
+ { file: "jest.config.js", command: "npx jest" },
221
+ ];
222
+ for (const { file, command } of testConfigs) {
223
+ if (await fileExistsAt(join(projectRoot, file))) {
224
+ result.testCommand = command;
225
+ break;
226
+ }
227
+ }
228
+ }
229
+ // 3. Check for tsconfig.json → tsc CI check
230
+ if (await fileExistsAt(join(projectRoot, "tsconfig.json"))) {
231
+ result.ciChecks.push({ name: "TypeScript", command: "npx tsc --noEmit" });
232
+ }
233
+ // 4. Check for eslint config → eslint CI check
234
+ const eslintConfigs = [
235
+ "eslint.config.js",
236
+ "eslint.config.ts",
237
+ "eslint.config.mjs",
238
+ ".eslintrc.js",
239
+ ".eslintrc.json",
240
+ ".eslintrc.yml",
241
+ ];
242
+ for (const file of eslintConfigs) {
243
+ if (await fileExistsAt(join(projectRoot, file))) {
244
+ result.ciChecks.push({ name: "ESLint", command: "npx eslint ." });
245
+ break;
246
+ }
247
+ }
248
+ return result;
249
+ }
199
250
  // ─── Action Prompt Builder ───────────────────────────────────────────────────
200
251
  /**
201
252
  * Builds the prompt to send to the agent based on the step's agent role and action.
202
253
  */
203
- export function buildActionPrompt(step, featureId, inputContent, artifactDir, availableSkills, baseBranch, testingConfig, projectRoot) {
254
+ export function buildActionPrompt(step, featureId, inputContent, artifactDir, baseBranch, testingConfig, projectRoot) {
204
255
  const parts = [];
205
256
  parts.push(`You are working on feature "${featureId}".`);
206
257
  if (projectRoot) {
@@ -211,13 +262,6 @@ export function buildActionPrompt(step, featureId, inputContent, artifactDir, av
211
262
  parts.push(`## Task: ${step.action}`);
212
263
  parts.push("");
213
264
  parts.push(getActionInstructions(step.agent, step.action, baseBranch));
214
- if (availableSkills) {
215
- const skillInstruction = SKILL_SEARCH_INSTRUCTIONS[step.action];
216
- if (skillInstruction) {
217
- parts.push("");
218
- parts.push(skillInstruction);
219
- }
220
- }
221
265
  if (inputContent) {
222
266
  parts.push("");
223
267
  parts.push("## Input Artifacts");
@@ -290,18 +334,52 @@ function getActionInstructions(agent, action, baseBranch) {
290
334
  case "fix":
291
335
  return "Fix the bug as described in the diagnosis. Apply the minimal change necessary.";
292
336
  case "test":
293
- return "Write comprehensive tests for the feature and run them. Produce a test-report.md with results.";
337
+ return baseBranch
338
+ ? "Write comprehensive tests for the feature and run them. Produce a test-report.md with results.\n\n" +
339
+ `**Execution order**: ` +
340
+ `1) Run \`git diff ${baseBranch}..HEAD --name-only\` to identify changed files. ` +
341
+ `2) Read the implementation files to understand types and interfaces. ` +
342
+ `3) Write ALL test files before running anything. ` +
343
+ `4) Run the test suite ONCE. If tests fail, fix all failures, then run ONCE more. ` +
344
+ `5) Run CI verification commands (type-check, lint) ONCE at the end, after all tests pass. ` +
345
+ `Do not interleave writing and running — batch all writes first, then run.`
346
+ : "Write comprehensive tests for the feature and run them. Produce a test-report.md with results.\n\n" +
347
+ "**Execution order**: " +
348
+ "1) Read the implementation files to understand types and interfaces. " +
349
+ "2) Write ALL test files before running anything. " +
350
+ "3) Run the test suite ONCE. If tests fail, fix all failures, then run ONCE more. " +
351
+ "4) Run CI verification commands (type-check, lint) ONCE at the end, after all tests pass. " +
352
+ "Do not interleave writing and running — batch all writes first, then run.";
294
353
  case "regression":
295
- return "Run existing tests to verify no regressions were introduced. Produce a regression-report.md with results.";
354
+ return baseBranch
355
+ ? "Run existing tests to verify no regressions were introduced. Produce a regression-report.md with results.\n\n" +
356
+ `**Execution order**: ` +
357
+ `1) Run \`git diff ${baseBranch}..HEAD --name-only\` to identify changed files and test files. ` +
358
+ `2) Run the full test suite ONCE with a single command. ` +
359
+ `3) Run CI verification commands (type-check, lint) ONCE at the end. ` +
360
+ `Do not run tests file-by-file — use a single test runner invocation.`
361
+ : "Run existing tests to verify no regressions were introduced. Produce a regression-report.md with results.\n\n" +
362
+ "**Execution order**: " +
363
+ "1) Identify the test files to run from test-report.md. " +
364
+ "2) Run the full test suite ONCE with a single command. " +
365
+ "3) Run CI verification commands (type-check, lint) ONCE at the end. " +
366
+ "Do not run tests file-by-file — use a single test runner invocation.";
296
367
  case "review":
297
- return "Review the implementation for correctness, quality, architecture compliance, and standards compliance. Produce a review.md with your verdict.";
368
+ return baseBranch
369
+ ? `Review the implementation for correctness, quality, architecture compliance, and standards compliance. Produce a review.md with your verdict.\n\n` +
370
+ `**Start by running \`git diff ${baseBranch}..HEAD\`** to see all changes at once. ` +
371
+ `Use \`git diff --stat ${baseBranch}..HEAD\` for an overview.`
372
+ : "Review the implementation for correctness, quality, architecture compliance, and standards compliance. Produce a review.md with your verdict.";
298
373
  case "branch":
299
374
  return baseBranch
300
375
  ? `Create a feature branch from \`${baseBranch}\`. Run \`git checkout ${baseBranch}\` first, then create the branch.`
301
376
  : `Create a feature branch for ${agent === "cicd" ? "this feature" : "the implementation"}.`;
302
377
  case "merge":
303
378
  return baseBranch
304
- ? `Merge the feature branch into \`${baseBranch}\` using \`--no-ff\`. Run \`git checkout ${baseBranch}\` first, then merge. Do NOT run \`git push\` — push is handled by the orchestration system.`
379
+ ? `Merge the feature branch into \`${baseBranch}\` using \`--no-ff\`.\n\n` +
380
+ `**Start by running \`git diff ${baseBranch}..HEAD --name-only\`** to get the complete list of changed files. ` +
381
+ `Use this list for \`git add\` in a single command — do NOT use \`ls\`, \`find\`, or \`git status\` to discover files.\n\n` +
382
+ `Run \`git checkout ${baseBranch}\` first, then merge. Do NOT run \`git push\` — push is handled by the orchestration system.`
305
383
  : "Merge the feature branch after all checks pass. Do NOT run `git push`.";
306
384
  case "analyze":
307
385
  return "Analyze the impact of the proposed change on the existing codebase. Produce an impact-report.md.";
@@ -338,8 +416,27 @@ export const defaultStepExecutor = async (step, featureId, context) => {
338
416
  const systemPrompt = getSystemPromptForRole(step.agent);
339
417
  // 4. Artifact directory path
340
418
  const artifactDir = store.getFeatureDir(featureId);
419
+ // 4b. Lightweight test config detection for new projects
420
+ // When config.testing.testCommand is empty (project-analyze ran before project existed),
421
+ // detect from package.json and config files so the tester has concrete commands to use.
422
+ let effectiveTestingConfig = config.testing;
423
+ if (step.agent === "tester" &&
424
+ (step.action === "test" || step.action === "regression") &&
425
+ !config.testing?.testCommand) {
426
+ const detected = await detectTestConfig(projectRoot);
427
+ if (detected.testCommand || detected.ciChecks.length > 0) {
428
+ effectiveTestingConfig = {
429
+ testCommand: detected.testCommand ?? "",
430
+ testTimeout: config.testing?.testTimeout ?? 300,
431
+ runExistingTests: config.testing?.runExistingTests ?? false,
432
+ excludePatterns: config.testing?.excludePatterns ?? [],
433
+ ciChecks: [...(config.testing?.ciChecks ?? []), ...detected.ciChecks],
434
+ };
435
+ log.debug(`Detected test config for new project: testCommand=${detected.testCommand ?? "(none)"}, ciChecks=${detected.ciChecks.length}`, { featureId });
436
+ }
437
+ }
341
438
  // 5. Build execution prompt
342
- let actionPrompt = buildActionPrompt(step, featureId, inputContent, artifactDir, context.availableSkills, config.baseBranch, config.testing, projectRoot);
439
+ let actionPrompt = buildActionPrompt(step, featureId, inputContent, artifactDir, config.baseBranch, effectiveTestingConfig, projectRoot);
343
440
  // 6a. Load prior action history for the same role (only when step opts in)
344
441
  let initialMessages;
345
442
  if (step.inheritPriorHistory) {
@@ -372,7 +469,6 @@ export const defaultStepExecutor = async (step, featureId, context) => {
372
469
  featureContext: inputContent || undefined,
373
470
  getApiKey: context.getApiKey,
374
471
  initialMessages,
375
- availableSkills: context.availableSkills,
376
472
  });
377
473
  // 8. Agent registration callback
378
474
  context.onAgentCreated?.(agent);
@@ -1 +1 @@
1
- {"version":3,"file":"step-executor.js","sourceRoot":"","sources":["../src/step-executor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACtG,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE7D,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAG1D,wLAAgF;AAEhF,iHAAiH;AACjH,MAAM,cAAc,GAA2B,IAAI,GAAG,CAAY;IACjE,SAAS;IACT,UAAU;IACV,UAAU;IACV,eAAe;IACf,WAAW;IACX,iBAAiB;CACjB,CAAC,CAAC;AAEH,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAAC,IAAe,EAAW;IACxD,OAAO,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAAA,CAChC;AAED,wLAAgF;AAEhF,uEAAuE;AACvE,MAAM,CAAC,MAAM,mBAAmB,GAA2B,IAAI,GAAG,CAAY;IAC7E,WAAW;IACX,UAAU;IACV,WAAW;IACX,QAAQ;IACR,eAAe;CACf,CAAC,CAAC;AAEH,oMAAgF;AAEhF;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,QAAwB,EAAyB;IAC/E,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,KAAK,CAAC;IAElB,KAAK,MAAM,GAAG,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,IAAI,OAAO,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YAC7D,MAAM,CAAC,GAAG,GAAG,CAAC,KAOb,CAAC;YACF,KAAK,GAAG,IAAI,CAAC;YACb,KAAK,IAAI,CAAC,CAAC,KAAK,CAAC;YACjB,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC;YACnB,SAAS,IAAI,CAAC,CAAC,SAAS,CAAC;YACzB,UAAU,IAAI,CAAC,CAAC,UAAU,CAAC;YAC3B,WAAW,IAAI,CAAC,CAAC,WAAW,CAAC;YAC7B,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC;QACtB,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAAA,CACvF;AAED,4LAAgF;AAEhF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAgB,EAAE,SAAiB,EAAE,OAAoB,EAAmB;IACpH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAC;IACX,CAAC;IAED,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,KAAqB,CAAC,CAAC;QAClF,IAAI,CAAC,OAAO,EAAE,CAAC;YACd,SAAS;QACV,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,KAAqB,CAAC,CAAC;QACnF,QAAQ,CAAC,IAAI,CAAC,aAAa,KAAK,OAAO,OAAO,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,OAAO,QAAQ,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAAA,CACpC;AAED,gMAAgF;AAEhF,SAAS,WAAW,CAAC,GAAW,EAAU;IACzC,OAAO,GAAG,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAAA,CAClD;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CACrC,YAAoB,EACpB,YAAoB,EACpB,cAAc,GAAY,KAAK,EACf;IAChB,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,uEAAuE;IACvE,8EAA8E;IAC9E,iFAAiF;IACjF,+EAA+E;IAC/E,6EAA6E;IAC7E,qCAAqC;IACrC,MAAM,QAAQ,GAAG,oBAAoB,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IAClE,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;QACvB,OAAO,QAAQ,CAAC;IACjB,CAAC;IAED,uFAAuF;IACvF,MAAM,QAAQ,GAAG,IAAI,MAAM,CAAC,SAAS,WAAW,CAAC,YAAY,CAAC,mCAAmC,CAAC,CAAC;IACnG,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC3C,IAAI,MAAM,EAAE,CAAC;QACZ,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,6DAA6D;IAC7D,IAAI,cAAc,EAAE,CAAC;QACpB,OAAO,YAAY,CAAC,IAAI,EAAE,CAAC;IAC5B,CAAC;IAED,OAAO,IAAI,CAAC;AAAA,CACZ;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB,CAAC,IAAY,EAAE,YAAoB,EAAiB;IAChF,oFAAoF;IACpF,MAAM,WAAW,GAAG,IAAI,MAAM,CAAC,YAAY,WAAW,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;IACrF,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,SAAS,EAAE,CAAC;QAChB,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,aAAa,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IAC1C,MAAM,YAAY,GAAG,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,qBAAqB;IAErF,sEAAsE;IACtE,uEAAuE;IACvE,0EAAwE;IACxE,8DAA8D;IAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,+BAA+B,CAAC;IAQrD,MAAM,OAAO,GAAkB,EAAE,CAAC;IAClC,SAAS,CAAC;QACT,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,KAAK,KAAK,IAAI;YAAE,MAAM;QAC1B,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAElC,wEAAwE;QACxE,IAAI,UAAU,KAAK,EAAE,IAAI,oBAAoB,CAAC,UAAU,CAAC,EAAE,CAAC;YAC3D,MAAM;QACP,CAAC;QAED,OAAO,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM;YAC1B,UAAU;SACV,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACb,CAAC;IAED,4DAA4D;IAC5D,4EAA4E;IAC5E,yEAAyE;IACzE,mBAAmB;IACnB,KAAK,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,CAAC,UAAU,KAAK,EAAE,IAAI,CAAC,CAAC,SAAS,IAAI,aAAa,EAAE,CAAC;YACzD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;YACvC,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;QACvB,CAAC;IACF,CAAC;IAED,gFAA8E;IAC9E,OAAO,IAAI,CAAC;AAAA,CACZ;AAED;;;;;;;GAOG;AACH,SAAS,oBAAoB,CAAC,UAAkB,EAAW;IAC1D,OAAO,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAAA,CAC5D;AAED,oLAAgF;AAEhF;;;GAGG;AACH,MAAM,yBAAyB,GAAqC;IACnE,IAAI,EAAE,+GAA+G;IACrH,MAAM,EAAE,0GAA0G;IAClH,IAAI,EAAE,sIAAsI;IAC5I,UAAU,EACT,6GAA6G;IAC9G,MAAM,EAAE,iGAAiG;IACzG,WAAW,EAAE,oFAAoF;IACjG,QAAQ,EAAE,4EAA4E;IACtF,SAAS,EACR,+HAA+H;IAChI,GAAG,EAAE,6GAA6G;IAClH,OAAO,EACN,8GAA8G;CAC/G,CAAC;AAEF,4LAAgF;AAEhF;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAChC,IAAkB,EAClB,SAAiB,EACjB,YAAoB,EACpB,WAAmB,EACnB,eAAwB,EACxB,UAAmB,EACnB,aAA6B,EAC7B,WAAoB,EACX;IACT,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,KAAK,CAAC,IAAI,CAAC,+BAA+B,SAAS,IAAI,CAAC,CAAC;IACzD,IAAI,WAAW,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CACT,yBAAyB,WAAW,kDAAkD;YACrF,uGAAuG,CACxG,CAAC;IACH,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACtC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACf,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IAEvE,IAAI,eAAe,EAAE,CAAC;QACrB,MAAM,gBAAgB,GAAG,yBAAyB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAChE,IAAI,gBAAgB,EAAE,CAAC;YACtB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC9B,CAAC;IACF,CAAC;IAED,IAAI,YAAY,EAAE,CAAC;QAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QACjC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAClC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;QACxD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACnC,KAAK,CAAC,IAAI,CAAC,OAAO,MAAM,YAAU,WAAW,IAAI,MAAM,IAAI,CAAC,CAAC;QAC9D,CAAC;QAED,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAChC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CACT,6EAA6E;gBAC5E,0EAA0E;gBAC1E,2CAA2C,CAC5C,CAAC;QACH,CAAC;aAAM,CAAC;YACP,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,2EAA2E,CAAC,CAAC;QACzF,CAAC;IACF,CAAC;IAED,yDAAyD;IACzD,IAAI,aAAa,EAAE,WAAW,EAAE,CAAC;QAChC,MAAM,YAAY,GACjB,CAAC,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,WAAW,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;YACtF,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC;QAEvF,IAAI,YAAY,EAAE,CAAC;YAClB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;YAC5C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,yBAAyB,aAAa,CAAC,WAAW,IAAI,CAAC,CAAC;YACnE,KAAK,CAAC,IAAI,CAAC,kBAAkB,aAAa,CAAC,WAAW,UAAU,CAAC,CAAC;YAClE,KAAK,CAAC,IAAI,CAAC,6BAA6B,aAAa,CAAC,gBAAgB,EAAE,CAAC,CAAC;YAC1E,IAAI,aAAa,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9C,KAAK,CAAC,IAAI,CAAC,2BAA2B,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACnF,CAAC;QACF,CAAC;IACF,CAAC;IAED,sFAAsF;IACtF,IAAI,aAAa,EAAE,QAAQ,IAAI,aAAa,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAClE,MAAM,oBAAoB,GACzB,CAAC,IAAI,CAAC,KAAK,KAAK,WAAW,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,WAAW,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;YACtF,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,YAAY,CAAC,CAAC,CAAC;QAEvF,IAAI,oBAAoB,EAAE,CAAC;YAC1B,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;YAC1C,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,CAAC,IAAI,CACT,oGAAoG,CACpG,CAAC;YACF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACf,KAAK,MAAM,KAAK,IAAI,aAAa,CAAC,QAAQ,EAAE,CAAC;gBAC5C,KAAK,CAAC,IAAI,CAAC,OAAO,KAAK,CAAC,IAAI,SAAS,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC;YACzD,CAAC;QACF,CAAC;IACF,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAAA,CACxB;AAED;;GAEG;AACH,SAAS,qBAAqB,CAAC,KAAgB,EAAE,MAAc,EAAE,UAAmB,EAAU;IAC7F,QAAQ,MAAM,EAAE,CAAC;QAChB,KAAK,MAAM;YACV,OAAO,qHAAqH,CAAC;QAE9H,KAAK,QAAQ;YACZ,OAAO,iHAAiH,CAAC;QAE1H,KAAK,WAAW;YACf,OAAO,oHAAoH,CAAC;QAE7H,KAAK,KAAK;YACT,OAAO,gFAAgF,CAAC;QAEzF,KAAK,MAAM;YACV,OAAO,gGAAgG,CAAC;QAEzG,KAAK,YAAY;YAChB,OAAO,2GAA2G,CAAC;QAEpH,KAAK,QAAQ;YACZ,OAAO,+IAA+I,CAAC;QAExJ,KAAK,QAAQ;YACZ,OAAO,UAAU;gBAChB,CAAC,CAAC,kCAAkC,UAAU,0BAA0B,UAAU,mCAAmC;gBACrH,CAAC,CAAC,+BAA+B,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,oBAAoB,GAAG,CAAC;QAE/F,KAAK,OAAO;YACX,OAAO,UAAU;gBAChB,CAAC,CAAC,mCAAmC,UAAU,4CAA4C,UAAU,gGAA8F;gBACnM,CAAC,CAAC,wEAAwE,CAAC;QAE7E,KAAK,SAAS;YACb,OAAO,kGAAkG,CAAC;QAE3G,KAAK,aAAa;YACjB,OAAO,gRAA8Q,CAAC;QAEvR,KAAK,UAAU;YACd,OAAO,qFAAqF,CAAC;QAE9F;YACC,OAAO,gBAAgB,MAAM,0BAA0B,CAAC;IAC1D,CAAC;AAAA,CACD;AAED,4LAAgF;AAEhF;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAiB,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,EAAE,CAAC;IACpF,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAC/C,MAAM,GAAG,GAAG,kBAAkB,CAAC,OAAO,CAAC,MAAM,IAAI,UAAU,EAAE,eAAe,CAAC,CAAC;IAE9E,qCAAqC;IACrC,MAAM,KAAK,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC;IAExC,0BAA0B;IAC1B,GAAG,CAAC,KAAK,CAAC,6BAA6B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;IACpG,IAAI,YAAY,GAAG,MAAM,kBAAkB,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAE7E,oFAAoF;IACpF,IAAI,mBAAmB,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,eAAe,EAAE,CAAC,EAAE,CAAC;QAC5E,MAAM,YAAY,GAAG,MAAM,KAAK,CAAC,gBAAgB,EAAE,CAAC;QACpD,MAAM,mBAAmB,GAAG,uDAAuD,YAAY,EAAE,CAAC;QAClG,YAAY,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,YAAY,cAAc,mBAAmB,EAAE,CAAC,CAAC,CAAC,mBAAmB,CAAC;QACvG,GAAG,CAAC,KAAK,CAAC,kCAAkC,IAAI,CAAC,KAAK,UAAU,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,uBAAuB;IACvB,MAAM,YAAY,GAAG,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAExD,6BAA6B;IAC7B,MAAM,WAAW,GAAG,KAAK,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;IAEnD,4BAA4B;IAC5B,IAAI,YAAY,GAAG,iBAAiB,CACnC,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,WAAW,EACX,OAAO,CAAC,eAAe,EACvB,MAAM,CAAC,UAAU,EACjB,MAAM,CAAC,OAAO,EACd,WAAW,CACX,CAAC;IAEF,2EAA2E;IAC3E,IAAI,eAAiF,CAAC;IACtF,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;QAC9B,MAAM,gBAAgB,GAAG,MAAM,KAAK,CAAC,qBAAqB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC/F,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACjC,GAAG,CAAC,KAAK,CAAC,oCAAoC,IAAI,CAAC,KAAK,KAAK,gBAAgB,CAAC,MAAM,gBAAgB,EAAE;gBACrG,SAAS;gBACT,KAAK,EAAE,IAAI,CAAC,KAAK;aACjB,CAAC,CAAC;YACH,eAAe,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC;QACjE,CAAC;IACF,CAAC;IAED,kGAAkG;IAClG,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,eAAe,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IACnF,IAAI,UAAU,EAAE,CAAC;QAChB,MAAM,UAAU,GAAG,MAAM,KAAK,CAAC,gBAAgB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACpF,IAAI,UAAU,EAAE,CAAC;YAChB,eAAe,GAAG,CAAC,GAAG,CAAC,eAAe,IAAI,EAAE,CAAC,EAAE,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;YACvE,YAAY,GAAG,uIAAuI,YAAY,EAAE,CAAC;QACtK,CAAC;IACF,CAAC;IAED,yCAAyC;IACzC,GAAG,CAAC,IAAI,CAAC,mBAAmB,IAAI,CAAC,KAAK,gBAAgB,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC;IACnH,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC;QACnC,IAAI,EAAE,IAAI,CAAC,KAAK;QAChB,YAAY;QACZ,MAAM;QACN,WAAW;QACX,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,cAAc,EAAE,YAAY,IAAI,SAAS;QACzC,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,eAAe;QACf,eAAe,EAAE,OAAO,CAAC,eAAe;KACxC,CAAC,CAAC;IAEH,iCAAiC;IACjC,OAAO,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,CAAC;IAEhC,IAAI,CAAC;QACJ,8BAA8B;QAC9B,GAAG,CAAC,IAAI,CAAC,oBAAoB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;QAE9D,sDAAsD;QACtD,OAAO,CAAC,aAAa,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACxD,OAAO,CAAC,oBAAoB,GAAG,MAAM,CAAC,IAAI,CAAC;QAE3C,8BAA8B;QAC9B,MAAM,KAAK,CAAC,gBAAgB,CAAC,SAAS,EAAE;YACvC,IAAI,EAAE,IAAI,CAAC,KAAK;YAChB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,0BAA0B,CAAC,MAAM,CAAC,QAAQ,CAAC;YACrD,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACnC,CAAC,CAAC;QAEH,wCAAwC;QACxC,IAAI,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3D,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC;YAC3C,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBACnC,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;gBACtE,IAAI,OAAO,EAAE,CAAC;oBACb,GAAG,CAAC,KAAK,CAAC,uBAAuB,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;oBAC7E,MAAM,KAAK,CAAC,aAAa,CAAC,SAAS,EAAE,MAAsB,EAAE,OAAO,CAAC,CAAC;gBACvE,CAAC;qBAAM,CAAC;oBACP,GAAG,CAAC,IAAI,CAAC,+BAA+B,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;gBACrF,CAAC;YACF,CAAC;QACF,CAAC;QACD,GAAG,CAAC,IAAI,CAAC,oBAAoB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAC1E,CAAC;YAAS,CAAC;QACV,6BAA6B;QAC7B,OAAO,CAAC,eAAe,EAAE,EAAE,CAAC;IAC7B,CAAC;AAAA,CACD,CAAC","sourcesContent":["import type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport { createRoleAgent, filterSerializableMessages, runAgentWithHistory } from \"./agent-factory.js\";\nimport { createModuleLogger, noopLogger } from \"./logger.js\";\nimport type { StepContext, StepExecutor, StepUsage } from \"./pipeline.js\";\nimport { getSystemPromptForRole } from \"./roles/index.js\";\nimport type { AgentRole, ArtifactName, PipelineStep, TestingConfig } from \"./types.js\";\n\n// ─── Readonly Role Detection ─────────────────────────────────────────────────\n\n/** Roles that cannot write files via tools. Artifacts are extracted from the response and saved to the store. */\nconst READONLY_ROLES: ReadonlySet<AgentRole> = new Set<AgentRole>([\n\t\"planner\",\n\t\"analyzer\",\n\t\"reviewer\",\n\t\"diagnostician\",\n\t\"discovery\",\n\t\"projectAnalyzer\",\n]);\n\n/** Determines whether the role is readonly (cannot write files via tools). */\nexport function isReadonlyRole(role: AgentRole): boolean {\n\treturn READONLY_ROLES.has(role);\n}\n\n// ─── System Design Injection ─────────────────────────────────────────────────\n\n/** Roles that receive system-design.md as additional input context. */\nexport const SYSTEM_DESIGN_ROLES: ReadonlySet<AgentRole> = new Set<AgentRole>([\n\t\"architect\",\n\t\"reviewer\",\n\t\"developer\",\n\t\"tester\",\n\t\"diagnostician\",\n]);\n\n// ─── Usage Aggregation ───────────────────────────────────────────────────────\n\n/**\n * Aggregates token usage from all assistant messages in a conversation.\n * Returns undefined if no assistant messages with usage data are found.\n */\nexport function aggregateUsage(messages: AgentMessage[]): StepUsage | undefined {\n\tlet input = 0;\n\tlet output = 0;\n\tlet cacheRead = 0;\n\tlet cacheWrite = 0;\n\tlet totalTokens = 0;\n\tlet cost = 0;\n\tlet found = false;\n\n\tfor (const msg of messages) {\n\t\tif (msg.role === \"assistant\" && \"usage\" in msg && msg.usage) {\n\t\t\tconst u = msg.usage as {\n\t\t\t\tinput: number;\n\t\t\t\toutput: number;\n\t\t\t\tcacheRead: number;\n\t\t\t\tcacheWrite: number;\n\t\t\t\ttotalTokens: number;\n\t\t\t\tcost: { total: number };\n\t\t\t};\n\t\t\tfound = true;\n\t\t\tinput += u.input;\n\t\t\toutput += u.output;\n\t\t\tcacheRead += u.cacheRead;\n\t\t\tcacheWrite += u.cacheWrite;\n\t\t\ttotalTokens += u.totalTokens;\n\t\t\tcost += u.cost.total;\n\t\t}\n\t}\n\n\treturn found ? { input, output, cacheRead, cacheWrite, totalTokens, cost } : undefined;\n}\n\n// ─── Input Artifact Loader ───────────────────────────────────────────────────\n\n/**\n * Reads the artifact files specified in step.inputs from the store\n * and combines them into a single context string.\n * Non-existent files are skipped.\n */\nexport async function loadInputArtifacts(inputs: string[], featureId: string, context: StepContext): Promise<string> {\n\tif (inputs.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst sections: string[] = [];\n\n\tfor (const input of inputs) {\n\t\tconst hasFile = await context.store.hasArtifact(featureId, input as ArtifactName);\n\t\tif (!hasFile) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst content = await context.store.readArtifact(featureId, input as ArtifactName);\n\t\tsections.push(`## Input: ${input}\\n\\n${content}`);\n\t}\n\n\treturn sections.join(\"\\n\\n---\\n\\n\");\n}\n\n// ─── Artifact Extraction ─────────────────────────────────────────────────────\n\nfunction escapeRegex(str: string): string {\n\treturn str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Extracts artifact content from agent response text.\n * Used when a readonly role outputs markdown artifacts as text.\n *\n * Extraction strategies:\n * 1. Extract from ```artifactName code block (with nested fence pairing)\n * 2. Extract from ## artifactName section\n * 3. Return the entire response if it's a single output\n */\nexport function extractArtifactContent(\n\tresponseText: string,\n\tartifactName: string,\n\tisSingleOutput: boolean = false,\n): string | null {\n\tif (!responseText.trim()) {\n\t\treturn null;\n\t}\n\n\t// Pattern 1: ```artifactName\\n...\\n``` with nested code fence pairing.\n\t// A simple non-greedy regex fails when the content itself contains ``` fences\n\t// (e.g., directory trees or code examples inside a project-context.md artifact).\n\t// Instead, we find the opening fence and then iterate through subsequent fence\n\t// markers, pairing them (inner-open, inner-close) until we find the unpaired\n\t// closing fence for the outer block.\n\tconst content1 = extractFromCodeFence(responseText, artifactName);\n\tif (content1 !== null) {\n\t\treturn content1;\n\t}\n\n\t// Pattern 2: Content after ## artifactName (without m flag so $ matches end of string)\n\tconst pattern2 = new RegExp(`##\\\\s+${escapeRegex(artifactName)}\\\\s*\\\\n([\\\\s\\\\S]*?)(?=\\\\n##\\\\s|$)`);\n\tconst match2 = pattern2.exec(responseText);\n\tif (match2) {\n\t\treturn match2[1].trim();\n\t}\n\n\t// Pattern 3: Use the entire response if it's a single output\n\tif (isSingleOutput) {\n\t\treturn responseText.trim();\n\t}\n\n\treturn null;\n}\n\n/**\n * Extracts content from a fenced code block that may contain nested code fences.\n *\n * Algorithm:\n * 1. Find the opening fence (``` followed by artifactName)\n * 2. After the opening, scan for all ``` markers on their own lines\n * 3. Inner fences pair up sequentially (open+close). The first unpaired marker\n * is the closing fence of the outer block.\n */\nfunction extractFromCodeFence(text: string, artifactName: string): string | null {\n\t// Match the opening fence line: ```artifactName (possibly with trailing whitespace)\n\tconst openPattern = new RegExp(`^(\\`{3,})${escapeRegex(artifactName)}[ \\\\t]*$`, \"m\");\n\tconst openMatch = openPattern.exec(text);\n\tif (!openMatch) {\n\t\treturn null;\n\t}\n\n\tconst openTickCount = openMatch[1].length;\n\tconst contentStart = openMatch.index + openMatch[0].length + 1; // +1 for the newline\n\n\t// Collect fence markers after the opening fence, stopping at the next\n\t// artifact-level opening fence (a fence whose info string looks like a\n\t// file path — contains '.' or '/'). This prevents consuming fences that\n\t// belong to subsequent artifacts in multi-artifact responses.\n\tconst rest = text.slice(contentStart);\n\tconst fencePattern = /^(`{3,})[ \\t]*(\\S*)?[ \\t]*$/gm;\n\n\tinterface FenceMarker {\n\t\tindex: number;\n\t\ttickCount: number;\n\t\tinfoString: string;\n\t}\n\n\tconst markers: FenceMarker[] = [];\n\tfor (;;) {\n\t\tconst match = fencePattern.exec(rest);\n\t\tif (match === null) break;\n\t\tconst infoString = match[2] || \"\";\n\n\t\t// Stop at the next artifact-level opening fence (file-like info string)\n\t\tif (infoString !== \"\" && isArtifactInfoString(infoString)) {\n\t\t\tbreak;\n\t\t}\n\n\t\tmarkers.push({\n\t\t\tindex: match.index,\n\t\t\ttickCount: match[1].length,\n\t\t\tinfoString,\n\t\t});\n\t}\n\n\tif (markers.length === 0) {\n\t\treturn null;\n\t}\n\n\t// Within the bounded markers, find the outer closing fence.\n\t// Use the last bare fence (no info string) with >= openTickCount backticks.\n\t// Inner bare fences pair up (open+close) before it; the remaining one is\n\t// the outer close.\n\tfor (let i = markers.length - 1; i >= 0; i--) {\n\t\tconst m = markers[i];\n\t\tif (m.infoString === \"\" && m.tickCount >= openTickCount) {\n\t\t\tconst content = rest.slice(0, m.index);\n\t\t\treturn content.trim();\n\t\t}\n\t}\n\n\t// No valid closing fence found — return null (fall through to other patterns)\n\treturn null;\n}\n\n/**\n * Determines if a code fence info string looks like an artifact file path\n * rather than a code language identifier.\n *\n * Artifact info strings contain '.' (file extension) or '/' (path separator),\n * e.g., \"spec.md\", \"feat-auth/spec.md\", \"plan.json\".\n * Code language info strings are simple identifiers like \"python\", \"bash\", \"json\", \"text\".\n */\nfunction isArtifactInfoString(infoString: string): boolean {\n\treturn infoString.includes(\".\") || infoString.includes(\"/\");\n}\n\n// ─── Skill Search Instructions ───────────────────────────────────────────────\n\n/**\n * Instructions for searching past artifacts to add to specific actions when availableSkills is present.\n * The \"past feature artifacts\" keyword matches the qmd-memory description in Available Skills.\n */\nconst SKILL_SEARCH_INSTRUCTIONS: Readonly<Record<string, string>> = {\n\tplan: \"Before planning, search past feature artifacts for similar features, design patterns, or recurring decisions.\",\n\tdesign: \"Before designing, search past feature artifacts for related design patterns and architectural decisions.\",\n\ttest: \"Search past feature artifacts for related test patterns, integration points, and previously discovered defects before writing tests.\",\n\tregression:\n\t\t\"Search past feature artifacts for areas previously affected by regressions before running regression tests.\",\n\treview: \"Search past feature artifacts for recurring review feedback and common issues before reviewing.\",\n\tinvestigate: \"Search past feature artifacts for similar issues and prior investigation findings.\",\n\tdiagnose: \"Search past feature artifacts for similar bug patterns and fix strategies.\",\n\timplement:\n\t\t\"Search past feature artifacts for similar implementation patterns, code conventions, and lessons learned before implementing.\",\n\tfix: \"Search past feature artifacts for similar fixes, related code areas, and previously applied fix strategies.\",\n\tanalyze:\n\t\t\"Search past feature artifacts for prior impact analyses, affected areas, and risk patterns before analyzing.\",\n};\n\n// ─── Action Prompt Builder ───────────────────────────────────────────────────\n\n/**\n * Builds the prompt to send to the agent based on the step's agent role and action.\n */\nexport function buildActionPrompt(\n\tstep: PipelineStep,\n\tfeatureId: string,\n\tinputContent: string,\n\tartifactDir: string,\n\tavailableSkills?: string,\n\tbaseBranch?: string,\n\ttestingConfig?: TestingConfig,\n\tprojectRoot?: string,\n): string {\n\tconst parts: string[] = [];\n\n\tparts.push(`You are working on feature \"${featureId}\".`);\n\tif (projectRoot) {\n\t\tparts.push(\n\t\t\t`The project root is \\`${projectRoot}\\`. All tool commands (bash, read, write, edit) ` +\n\t\t\t\t`execute relative to this directory. Do NOT prefix commands with \\`cd\\` to a different base directory.`,\n\t\t);\n\t}\n\tparts.push(\"\");\n\tparts.push(`## Task: ${step.action}`);\n\tparts.push(\"\");\n\tparts.push(getActionInstructions(step.agent, step.action, baseBranch));\n\n\tif (availableSkills) {\n\t\tconst skillInstruction = SKILL_SEARCH_INSTRUCTIONS[step.action];\n\t\tif (skillInstruction) {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(skillInstruction);\n\t\t}\n\t}\n\n\tif (inputContent) {\n\t\tparts.push(\"\");\n\t\tparts.push(\"## Input Artifacts\");\n\t\tparts.push(\"\");\n\t\tparts.push(inputContent);\n\t}\n\n\tif (step.outputs.length > 0) {\n\t\tparts.push(\"\");\n\t\tparts.push(\"## Output Artifacts\");\n\t\tparts.push(\"\");\n\t\tparts.push(\"You must produce the following artifacts:\");\n\t\tfor (const output of step.outputs) {\n\t\t\tparts.push(`- \\`${output}\\` → \\`${artifactDir}/${output}\\``);\n\t\t}\n\n\t\tif (isReadonlyRole(step.agent)) {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\n\t\t\t\t\"Since you cannot write files directly, output the content of each artifact \" +\n\t\t\t\t\t\"in a markdown code block with the artifact filename as the info string. \" +\n\t\t\t\t\t\"For example: ```spec.md\\\\n(content)\\\\n```\",\n\t\t\t);\n\t\t} else {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\"Write each artifact directly to the specified file path using your tools.\");\n\t\t}\n\t}\n\n\t// Inject testing config for developer and tester actions\n\tif (testingConfig?.testCommand) {\n\t\tconst shouldInject =\n\t\t\t(step.agent === \"developer\" && (step.action === \"implement\" || step.action === \"fix\")) ||\n\t\t\t(step.agent === \"tester\" && (step.action === \"test\" || step.action === \"regression\"));\n\n\t\tif (shouldInject) {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\"## Project Test Configuration\");\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(`- **Test command**: \\`${testingConfig.testCommand}\\``);\n\t\t\tparts.push(`- **Timeout**: ${testingConfig.testTimeout} seconds`);\n\t\t\tparts.push(`- **Run existing tests**: ${testingConfig.runExistingTests}`);\n\t\t\tif (testingConfig.excludePatterns.length > 0) {\n\t\t\t\tparts.push(`- **Exclude patterns**: ${testingConfig.excludePatterns.join(\", \")}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Inject CI checks for developer and tester actions (tester can write/edit files too)\n\tif (testingConfig?.ciChecks && testingConfig.ciChecks.length > 0) {\n\t\tconst shouldInjectCiChecks =\n\t\t\t(step.agent === \"developer\" && (step.action === \"implement\" || step.action === \"fix\")) ||\n\t\t\t(step.agent === \"tester\" && (step.action === \"test\" || step.action === \"regression\"));\n\n\t\tif (shouldInjectCiChecks) {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\"## CI Verification Commands\");\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\n\t\t\t\t\"You MUST run the following checks after implementation. All must pass before the task is complete.\",\n\t\t\t);\n\t\t\tparts.push(\"\");\n\t\t\tfor (const check of testingConfig.ciChecks) {\n\t\t\t\tparts.push(`- **${check.name}**: \\`${check.command}\\``);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\");\n}\n\n/**\n * Returns instructions for the given action.\n */\nfunction getActionInstructions(agent: AgentRole, action: string, baseBranch?: string): string {\n\tswitch (action) {\n\t\tcase \"plan\":\n\t\t\treturn \"Analyze the requirements and decompose them into features. Produce a spec.md for each feature and update plan.json.\";\n\n\t\tcase \"design\":\n\t\t\treturn \"Read the spec and produce a detailed design document with file structure, interfaces, and implementation guide.\";\n\n\t\tcase \"implement\":\n\t\t\treturn \"Implement the feature according to the spec and design documents. Write source code files and ensure they compile.\";\n\n\t\tcase \"fix\":\n\t\t\treturn \"Fix the bug as described in the diagnosis. Apply the minimal change necessary.\";\n\n\t\tcase \"test\":\n\t\t\treturn \"Write comprehensive tests for the feature and run them. Produce a test-report.md with results.\";\n\n\t\tcase \"regression\":\n\t\t\treturn \"Run existing tests to verify no regressions were introduced. Produce a regression-report.md with results.\";\n\n\t\tcase \"review\":\n\t\t\treturn \"Review the implementation for correctness, quality, architecture compliance, and standards compliance. Produce a review.md with your verdict.\";\n\n\t\tcase \"branch\":\n\t\t\treturn baseBranch\n\t\t\t\t? `Create a feature branch from \\`${baseBranch}\\`. Run \\`git checkout ${baseBranch}\\` first, then create the branch.`\n\t\t\t\t: `Create a feature branch for ${agent === \"cicd\" ? \"this feature\" : \"the implementation\"}.`;\n\n\t\tcase \"merge\":\n\t\t\treturn baseBranch\n\t\t\t\t? `Merge the feature branch into \\`${baseBranch}\\` using \\`--no-ff\\`. Run \\`git checkout ${baseBranch}\\` first, then merge. Do NOT run \\`git push\\` — push is handled by the orchestration system.`\n\t\t\t\t: \"Merge the feature branch after all checks pass. Do NOT run `git push`.\";\n\n\t\tcase \"analyze\":\n\t\t\treturn \"Analyze the impact of the proposed change on the existing codebase. Produce an impact-report.md.\";\n\n\t\tcase \"investigate\":\n\t\t\treturn \"Trace through the code to identify the root cause of the issue. Use read and bash tools to explore the codebase, run tests, add debug output if needed. Do not produce any artifacts — this is an exploratory step. Your findings will inform the subsequent diagnosis step.\";\n\n\t\tcase \"diagnose\":\n\t\t\treturn \"Analyze the bug, reproduce it, identify the root cause, and produce a diagnosis.md.\";\n\n\t\tdefault:\n\t\t\treturn `Execute the \"${action}\" task for this feature.`;\n\t}\n}\n\n// ─── Default Step Executor ───────────────────────────────────────────────────\n\n/**\n * Default StepExecutor implementation.\n * Creates a role-specific agent, passes input artifacts as context,\n * runs the agent, and saves the output artifacts.\n */\nexport const defaultStepExecutor: StepExecutor = async (step, featureId, context) => {\n\tconst { store, config, projectRoot } = context;\n\tconst log = createModuleLogger(context.logger ?? noopLogger, \"step-executor\");\n\n\t// 1. Ensure feature directory exists\n\tawait store.ensureFeatureDir(featureId);\n\n\t// 2. Load input artifacts\n\tlog.debug(`Loading input artifacts: [${step.inputs.join(\", \")}]`, { featureId, agent: step.agent });\n\tlet inputContent = await loadInputArtifacts(step.inputs, featureId, context);\n\n\t// 2b. Inject system-design.md for roles that need system-level architecture context\n\tif (SYSTEM_DESIGN_ROLES.has(step.agent) && (await store.hasSystemDesign())) {\n\t\tconst systemDesign = await store.readSystemDesign();\n\t\tconst systemDesignSection = `## Input: system-design.md (System Architecture)\\n\\n${systemDesign}`;\n\t\tinputContent = inputContent ? `${inputContent}\\n\\n---\\n\\n${systemDesignSection}` : systemDesignSection;\n\t\tlog.debug(`Injected system-design.md into ${step.agent} context`, { featureId });\n\t}\n\n\t// 3. Get system prompt\n\tconst systemPrompt = getSystemPromptForRole(step.agent);\n\n\t// 4. Artifact directory path\n\tconst artifactDir = store.getFeatureDir(featureId);\n\n\t// 5. Build execution prompt\n\tlet actionPrompt = buildActionPrompt(\n\t\tstep,\n\t\tfeatureId,\n\t\tinputContent,\n\t\tartifactDir,\n\t\tcontext.availableSkills,\n\t\tconfig.baseBranch,\n\t\tconfig.testing,\n\t\tprojectRoot,\n\t);\n\n\t// 6a. Load prior action history for the same role (only when step opts in)\n\tlet initialMessages: import(\"@mariozechner/pi-agent-core\").AgentMessage[] | undefined;\n\tif (step.inheritPriorHistory) {\n\t\tconst priorCheckpoints = await store.loadPriorAgentHistory(featureId, step.agent, step.action);\n\t\tif (priorCheckpoints.length > 0) {\n\t\t\tlog.debug(`Loading prior action history for ${step.agent}: ${priorCheckpoints.length} checkpoint(s)`, {\n\t\t\t\tfeatureId,\n\t\t\t\tagent: step.agent,\n\t\t\t});\n\t\t\tinitialMessages = priorCheckpoints.flatMap((cp) => cp.messages);\n\t\t}\n\t}\n\n\t// 6b. If retry history exists for the current action, append it (checkpoint restoration on retry)\n\tconst hasHistory = await store.hasAgentHistory(featureId, step.agent, step.action);\n\tif (hasHistory) {\n\t\tconst checkpoint = await store.loadAgentHistory(featureId, step.agent, step.action);\n\t\tif (checkpoint) {\n\t\t\tinitialMessages = [...(initialMessages ?? []), ...checkpoint.messages];\n\t\t\tactionPrompt = `[RETRY] Previous attempt for this step exists in your conversation history. Review what went wrong and try a different approach.\\n\\n${actionPrompt}`;\n\t\t}\n\t}\n\n\t// 7. Create agent (inject prior history)\n\tlog.info(`Creating agent: ${step.agent} for action: ${step.action}`, { featureId, hasHistory: !!initialMessages });\n\tconst agent = await createRoleAgent({\n\t\trole: step.agent,\n\t\tsystemPrompt,\n\t\tconfig,\n\t\tprojectRoot,\n\t\tmodel: context.model,\n\t\tfeatureContext: inputContent || undefined,\n\t\tgetApiKey: context.getApiKey,\n\t\tinitialMessages,\n\t\tavailableSkills: context.availableSkills,\n\t});\n\n\t// 8. Agent registration callback\n\tcontext.onAgentCreated?.(agent);\n\n\ttry {\n\t\t// 9. Run agent (with history)\n\t\tlog.info(`Agent executing: ${step.agent}:${step.action}`, { featureId });\n\t\tconst result = await runAgentWithHistory(agent, actionPrompt);\n\n\t\t// 9b. Aggregate token usage and capture response text\n\t\tcontext.lastStepUsage = aggregateUsage(result.messages);\n\t\tcontext.lastStepResponseText = result.text;\n\n\t\t// 10. Save history checkpoint\n\t\tawait store.saveAgentHistory(featureId, {\n\t\t\trole: step.agent,\n\t\t\taction: step.action,\n\t\t\tmessages: filterSerializableMessages(result.messages),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t});\n\n\t\t// 11. Save artifacts for readonly roles\n\t\tif (isReadonlyRole(step.agent) && step.outputs.length > 0) {\n\t\t\tconst isSingle = step.outputs.length === 1;\n\t\t\tfor (const output of step.outputs) {\n\t\t\t\tconst content = extractArtifactContent(result.text, output, isSingle);\n\t\t\t\tif (content) {\n\t\t\t\t\tlog.debug(`Extracted artifact: ${output}`, { featureId, agent: step.agent });\n\t\t\t\t\tawait store.writeArtifact(featureId, output as ArtifactName, content);\n\t\t\t\t} else {\n\t\t\t\t\tlog.warn(`Failed to extract artifact: ${output}`, { featureId, agent: step.agent });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlog.info(`Agent completed: ${step.agent}:${step.action}`, { featureId });\n\t} finally {\n\t\t// 12. Agent release callback\n\t\tcontext.onAgentFinished?.();\n\t}\n};\n"]}
1
+ {"version":3,"file":"step-executor.js","sourceRoot":"","sources":["../src/step-executor.ts"],"names":[],"mappings":"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","sourcesContent":["import { access, readFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nimport type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport { createRoleAgent, filterSerializableMessages, runAgentWithHistory } from \"./agent-factory.js\";\nimport { createModuleLogger, noopLogger } from \"./logger.js\";\nimport type { StepContext, StepExecutor, StepUsage } from \"./pipeline.js\";\nimport { getSystemPromptForRole } from \"./roles/index.js\";\nimport type { AgentRole, ArtifactName, CiCheck, PipelineStep, TestingConfig } from \"./types.js\";\n\n// ─── Readonly Role Detection ─────────────────────────────────────────────────\n\n/** Roles that cannot write files via tools. Artifacts are extracted from the response and saved to the store. */\nconst READONLY_ROLES: ReadonlySet<AgentRole> = new Set<AgentRole>([\n\t\"planner\",\n\t\"analyzer\",\n\t\"reviewer\",\n\t\"diagnostician\",\n\t\"discovery\",\n\t\"projectAnalyzer\",\n]);\n\n/** Determines whether the role is readonly (cannot write files via tools). */\nexport function isReadonlyRole(role: AgentRole): boolean {\n\treturn READONLY_ROLES.has(role);\n}\n\n// ─── System Design Injection ─────────────────────────────────────────────────\n\n/** Roles that receive system-design.md as additional input context. */\nexport const SYSTEM_DESIGN_ROLES: ReadonlySet<AgentRole> = new Set<AgentRole>([\n\t\"architect\",\n\t\"reviewer\",\n\t\"developer\",\n\t\"tester\",\n\t\"diagnostician\",\n]);\n\n// ─── Usage Aggregation ───────────────────────────────────────────────────────\n\n/**\n * Aggregates token usage from all assistant messages in a conversation.\n * Returns undefined if no assistant messages with usage data are found.\n */\nexport function aggregateUsage(messages: AgentMessage[]): StepUsage | undefined {\n\tlet input = 0;\n\tlet output = 0;\n\tlet cacheRead = 0;\n\tlet cacheWrite = 0;\n\tlet totalTokens = 0;\n\tlet cost = 0;\n\tlet found = false;\n\n\tfor (const msg of messages) {\n\t\tif (msg.role === \"assistant\" && \"usage\" in msg && msg.usage) {\n\t\t\tconst u = msg.usage as {\n\t\t\t\tinput: number;\n\t\t\t\toutput: number;\n\t\t\t\tcacheRead: number;\n\t\t\t\tcacheWrite: number;\n\t\t\t\ttotalTokens: number;\n\t\t\t\tcost: { total: number };\n\t\t\t};\n\t\t\tfound = true;\n\t\t\tinput += u.input;\n\t\t\toutput += u.output;\n\t\t\tcacheRead += u.cacheRead;\n\t\t\tcacheWrite += u.cacheWrite;\n\t\t\ttotalTokens += u.totalTokens;\n\t\t\tcost += u.cost.total;\n\t\t}\n\t}\n\n\treturn found ? { input, output, cacheRead, cacheWrite, totalTokens, cost } : undefined;\n}\n\n// ─── Input Artifact Loader ───────────────────────────────────────────────────\n\n/**\n * Reads the artifact files specified in step.inputs from the store\n * and combines them into a single context string.\n * Non-existent files are skipped.\n */\nexport async function loadInputArtifacts(inputs: string[], featureId: string, context: StepContext): Promise<string> {\n\tif (inputs.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst sections: string[] = [];\n\n\tfor (const input of inputs) {\n\t\tconst hasFile = await context.store.hasArtifact(featureId, input as ArtifactName);\n\t\tif (!hasFile) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst content = await context.store.readArtifact(featureId, input as ArtifactName);\n\t\tsections.push(`## Input: ${input}\\n\\n${content}`);\n\t}\n\n\treturn sections.join(\"\\n\\n---\\n\\n\");\n}\n\n// ─── Artifact Extraction ─────────────────────────────────────────────────────\n\nfunction escapeRegex(str: string): string {\n\treturn str.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n/**\n * Extracts artifact content from agent response text.\n * Used when a readonly role outputs markdown artifacts as text.\n *\n * Extraction strategies:\n * 1. Extract from ```artifactName code block (with nested fence pairing)\n * 2. Extract from ## artifactName section\n * 3. Return the entire response if it's a single output\n */\nexport function extractArtifactContent(\n\tresponseText: string,\n\tartifactName: string,\n\tisSingleOutput: boolean = false,\n): string | null {\n\tif (!responseText.trim()) {\n\t\treturn null;\n\t}\n\n\t// Pattern 1: ```artifactName\\n...\\n``` with nested code fence pairing.\n\t// A simple non-greedy regex fails when the content itself contains ``` fences\n\t// (e.g., directory trees or code examples inside a project-context.md artifact).\n\t// Instead, we find the opening fence and then iterate through subsequent fence\n\t// markers, pairing them (inner-open, inner-close) until we find the unpaired\n\t// closing fence for the outer block.\n\tconst content1 = extractFromCodeFence(responseText, artifactName);\n\tif (content1 !== null) {\n\t\treturn content1;\n\t}\n\n\t// Pattern 2: Content after ## artifactName (without m flag so $ matches end of string)\n\tconst pattern2 = new RegExp(`##\\\\s+${escapeRegex(artifactName)}\\\\s*\\\\n([\\\\s\\\\S]*?)(?=\\\\n##\\\\s|$)`);\n\tconst match2 = pattern2.exec(responseText);\n\tif (match2) {\n\t\treturn match2[1].trim();\n\t}\n\n\t// Pattern 3: Use the entire response if it's a single output\n\tif (isSingleOutput) {\n\t\treturn responseText.trim();\n\t}\n\n\treturn null;\n}\n\n/**\n * Extracts content from a fenced code block that may contain nested code fences.\n *\n * Algorithm:\n * 1. Find the opening fence (``` followed by artifactName)\n * 2. After the opening, scan for all ``` markers on their own lines\n * 3. Inner fences pair up sequentially (open+close). The first unpaired marker\n * is the closing fence of the outer block.\n */\nfunction extractFromCodeFence(text: string, artifactName: string): string | null {\n\t// Match the opening fence line: ```artifactName (possibly with trailing whitespace)\n\tconst openPattern = new RegExp(`^(\\`{3,})${escapeRegex(artifactName)}[ \\\\t]*$`, \"m\");\n\tconst openMatch = openPattern.exec(text);\n\tif (!openMatch) {\n\t\treturn null;\n\t}\n\n\tconst openTickCount = openMatch[1].length;\n\tconst contentStart = openMatch.index + openMatch[0].length + 1; // +1 for the newline\n\n\t// Collect fence markers after the opening fence, stopping at the next\n\t// artifact-level opening fence (a fence whose info string looks like a\n\t// file path — contains '.' or '/'). This prevents consuming fences that\n\t// belong to subsequent artifacts in multi-artifact responses.\n\tconst rest = text.slice(contentStart);\n\tconst fencePattern = /^(`{3,})[ \\t]*(\\S*)?[ \\t]*$/gm;\n\n\tinterface FenceMarker {\n\t\tindex: number;\n\t\ttickCount: number;\n\t\tinfoString: string;\n\t}\n\n\tconst markers: FenceMarker[] = [];\n\tfor (;;) {\n\t\tconst match = fencePattern.exec(rest);\n\t\tif (match === null) break;\n\t\tconst infoString = match[2] || \"\";\n\n\t\t// Stop at the next artifact-level opening fence (file-like info string)\n\t\tif (infoString !== \"\" && isArtifactInfoString(infoString)) {\n\t\t\tbreak;\n\t\t}\n\n\t\tmarkers.push({\n\t\t\tindex: match.index,\n\t\t\ttickCount: match[1].length,\n\t\t\tinfoString,\n\t\t});\n\t}\n\n\tif (markers.length === 0) {\n\t\treturn null;\n\t}\n\n\t// Within the bounded markers, find the outer closing fence.\n\t// Use the last bare fence (no info string) with >= openTickCount backticks.\n\t// Inner bare fences pair up (open+close) before it; the remaining one is\n\t// the outer close.\n\tfor (let i = markers.length - 1; i >= 0; i--) {\n\t\tconst m = markers[i];\n\t\tif (m.infoString === \"\" && m.tickCount >= openTickCount) {\n\t\t\tconst content = rest.slice(0, m.index);\n\t\t\treturn content.trim();\n\t\t}\n\t}\n\n\t// No valid closing fence found — return null (fall through to other patterns)\n\treturn null;\n}\n\n/**\n * Determines if a code fence info string looks like an artifact file path\n * rather than a code language identifier.\n *\n * Artifact info strings contain '.' (file extension) or '/' (path separator),\n * e.g., \"spec.md\", \"feat-auth/spec.md\", \"plan.json\".\n * Code language info strings are simple identifiers like \"python\", \"bash\", \"json\", \"text\".\n */\nfunction isArtifactInfoString(infoString: string): boolean {\n\treturn infoString.includes(\".\") || infoString.includes(\"/\");\n}\n\n// ─── Lightweight Test Config Detection ───────────────────────────────────────\n\n/** Result of lightweight test config detection from project files. */\nexport interface DetectedTestConfig {\n\ttestCommand?: string;\n\tciChecks: CiCheck[];\n}\n\n/** Checks whether a file exists at the given path. */\nasync function fileExistsAt(path: string): Promise<boolean> {\n\ttry {\n\t\tawait access(path);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Detects test command and CI checks from project files when config.testing is empty.\n * This is a fast file-existence check — NOT a full project-analyze re-run.\n * Used for new projects where project-analyze ran before the project scaffold existed.\n */\nexport async function detectTestConfig(projectRoot: string): Promise<DetectedTestConfig> {\n\tconst result: DetectedTestConfig = { ciChecks: [] };\n\n\t// 1. Read package.json scripts.test\n\ttry {\n\t\tconst raw = await readFile(join(projectRoot, \"package.json\"), \"utf-8\");\n\t\tconst pkg = JSON.parse(raw) as { scripts?: Record<string, string> };\n\t\tconst testScript = pkg.scripts?.test;\n\t\tif (testScript && testScript !== 'echo \"Error: no test specified\" && exit 1') {\n\t\t\tresult.testCommand = testScript;\n\t\t}\n\t} catch {\n\t\t/* no package.json */\n\t}\n\n\t// 2. Check for vitest/jest config → fallback test command\n\tif (!result.testCommand) {\n\t\tconst testConfigs: Array<{ file: string; command: string }> = [\n\t\t\t{ file: \"vitest.config.ts\", command: \"npx vitest run\" },\n\t\t\t{ file: \"vitest.config.js\", command: \"npx vitest run\" },\n\t\t\t{ file: \"vite.config.ts\", command: \"npx vitest run\" },\n\t\t\t{ file: \"jest.config.ts\", command: \"npx jest\" },\n\t\t\t{ file: \"jest.config.js\", command: \"npx jest\" },\n\t\t];\n\t\tfor (const { file, command } of testConfigs) {\n\t\t\tif (await fileExistsAt(join(projectRoot, file))) {\n\t\t\t\tresult.testCommand = command;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// 3. Check for tsconfig.json → tsc CI check\n\tif (await fileExistsAt(join(projectRoot, \"tsconfig.json\"))) {\n\t\tresult.ciChecks.push({ name: \"TypeScript\", command: \"npx tsc --noEmit\" });\n\t}\n\n\t// 4. Check for eslint config → eslint CI check\n\tconst eslintConfigs = [\n\t\t\"eslint.config.js\",\n\t\t\"eslint.config.ts\",\n\t\t\"eslint.config.mjs\",\n\t\t\".eslintrc.js\",\n\t\t\".eslintrc.json\",\n\t\t\".eslintrc.yml\",\n\t];\n\tfor (const file of eslintConfigs) {\n\t\tif (await fileExistsAt(join(projectRoot, file))) {\n\t\t\tresult.ciChecks.push({ name: \"ESLint\", command: \"npx eslint .\" });\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn result;\n}\n\n// ─── Action Prompt Builder ───────────────────────────────────────────────────\n\n/**\n * Builds the prompt to send to the agent based on the step's agent role and action.\n */\nexport function buildActionPrompt(\n\tstep: PipelineStep,\n\tfeatureId: string,\n\tinputContent: string,\n\tartifactDir: string,\n\tbaseBranch?: string,\n\ttestingConfig?: TestingConfig,\n\tprojectRoot?: string,\n): string {\n\tconst parts: string[] = [];\n\n\tparts.push(`You are working on feature \"${featureId}\".`);\n\tif (projectRoot) {\n\t\tparts.push(\n\t\t\t`The project root is \\`${projectRoot}\\`. All tool commands (bash, read, write, edit) ` +\n\t\t\t\t`execute relative to this directory. Do NOT prefix commands with \\`cd\\` to a different base directory.`,\n\t\t);\n\t}\n\tparts.push(\"\");\n\tparts.push(`## Task: ${step.action}`);\n\tparts.push(\"\");\n\tparts.push(getActionInstructions(step.agent, step.action, baseBranch));\n\n\tif (inputContent) {\n\t\tparts.push(\"\");\n\t\tparts.push(\"## Input Artifacts\");\n\t\tparts.push(\"\");\n\t\tparts.push(inputContent);\n\t}\n\n\tif (step.outputs.length > 0) {\n\t\tparts.push(\"\");\n\t\tparts.push(\"## Output Artifacts\");\n\t\tparts.push(\"\");\n\t\tparts.push(\"You must produce the following artifacts:\");\n\t\tfor (const output of step.outputs) {\n\t\t\tparts.push(`- \\`${output}\\` → \\`${artifactDir}/${output}\\``);\n\t\t}\n\n\t\tif (isReadonlyRole(step.agent)) {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\n\t\t\t\t\"Since you cannot write files directly, output the content of each artifact \" +\n\t\t\t\t\t\"in a markdown code block with the artifact filename as the info string. \" +\n\t\t\t\t\t\"For example: ```spec.md\\\\n(content)\\\\n```\",\n\t\t\t);\n\t\t} else {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\"Write each artifact directly to the specified file path using your tools.\");\n\t\t}\n\t}\n\n\t// Inject testing config for developer and tester actions\n\tif (testingConfig?.testCommand) {\n\t\tconst shouldInject =\n\t\t\t(step.agent === \"developer\" && (step.action === \"implement\" || step.action === \"fix\")) ||\n\t\t\t(step.agent === \"tester\" && (step.action === \"test\" || step.action === \"regression\"));\n\n\t\tif (shouldInject) {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\"## Project Test Configuration\");\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(`- **Test command**: \\`${testingConfig.testCommand}\\``);\n\t\t\tparts.push(`- **Timeout**: ${testingConfig.testTimeout} seconds`);\n\t\t\tparts.push(`- **Run existing tests**: ${testingConfig.runExistingTests}`);\n\t\t\tif (testingConfig.excludePatterns.length > 0) {\n\t\t\t\tparts.push(`- **Exclude patterns**: ${testingConfig.excludePatterns.join(\", \")}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Inject CI checks for developer and tester actions (tester can write/edit files too)\n\tif (testingConfig?.ciChecks && testingConfig.ciChecks.length > 0) {\n\t\tconst shouldInjectCiChecks =\n\t\t\t(step.agent === \"developer\" && (step.action === \"implement\" || step.action === \"fix\")) ||\n\t\t\t(step.agent === \"tester\" && (step.action === \"test\" || step.action === \"regression\"));\n\n\t\tif (shouldInjectCiChecks) {\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\"## CI Verification Commands\");\n\t\t\tparts.push(\"\");\n\t\t\tparts.push(\n\t\t\t\t\"You MUST run the following checks after implementation. All must pass before the task is complete.\",\n\t\t\t);\n\t\t\tparts.push(\"\");\n\t\t\tfor (const check of testingConfig.ciChecks) {\n\t\t\t\tparts.push(`- **${check.name}**: \\`${check.command}\\``);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\");\n}\n\n/**\n * Returns instructions for the given action.\n */\nfunction getActionInstructions(agent: AgentRole, action: string, baseBranch?: string): string {\n\tswitch (action) {\n\t\tcase \"plan\":\n\t\t\treturn \"Analyze the requirements and decompose them into features. Produce a spec.md for each feature and update plan.json.\";\n\n\t\tcase \"design\":\n\t\t\treturn \"Read the spec and produce a detailed design document with file structure, interfaces, and implementation guide.\";\n\n\t\tcase \"implement\":\n\t\t\treturn \"Implement the feature according to the spec and design documents. Write source code files and ensure they compile.\";\n\n\t\tcase \"fix\":\n\t\t\treturn \"Fix the bug as described in the diagnosis. Apply the minimal change necessary.\";\n\n\t\tcase \"test\":\n\t\t\treturn baseBranch\n\t\t\t\t? \"Write comprehensive tests for the feature and run them. Produce a test-report.md with results.\\n\\n\" +\n\t\t\t\t\t\t`**Execution order**: ` +\n\t\t\t\t\t\t`1) Run \\`git diff ${baseBranch}..HEAD --name-only\\` to identify changed files. ` +\n\t\t\t\t\t\t`2) Read the implementation files to understand types and interfaces. ` +\n\t\t\t\t\t\t`3) Write ALL test files before running anything. ` +\n\t\t\t\t\t\t`4) Run the test suite ONCE. If tests fail, fix all failures, then run ONCE more. ` +\n\t\t\t\t\t\t`5) Run CI verification commands (type-check, lint) ONCE at the end, after all tests pass. ` +\n\t\t\t\t\t\t`Do not interleave writing and running — batch all writes first, then run.`\n\t\t\t\t: \"Write comprehensive tests for the feature and run them. Produce a test-report.md with results.\\n\\n\" +\n\t\t\t\t\t\t\"**Execution order**: \" +\n\t\t\t\t\t\t\"1) Read the implementation files to understand types and interfaces. \" +\n\t\t\t\t\t\t\"2) Write ALL test files before running anything. \" +\n\t\t\t\t\t\t\"3) Run the test suite ONCE. If tests fail, fix all failures, then run ONCE more. \" +\n\t\t\t\t\t\t\"4) Run CI verification commands (type-check, lint) ONCE at the end, after all tests pass. \" +\n\t\t\t\t\t\t\"Do not interleave writing and running — batch all writes first, then run.\";\n\n\t\tcase \"regression\":\n\t\t\treturn baseBranch\n\t\t\t\t? \"Run existing tests to verify no regressions were introduced. Produce a regression-report.md with results.\\n\\n\" +\n\t\t\t\t\t\t`**Execution order**: ` +\n\t\t\t\t\t\t`1) Run \\`git diff ${baseBranch}..HEAD --name-only\\` to identify changed files and test files. ` +\n\t\t\t\t\t\t`2) Run the full test suite ONCE with a single command. ` +\n\t\t\t\t\t\t`3) Run CI verification commands (type-check, lint) ONCE at the end. ` +\n\t\t\t\t\t\t`Do not run tests file-by-file — use a single test runner invocation.`\n\t\t\t\t: \"Run existing tests to verify no regressions were introduced. Produce a regression-report.md with results.\\n\\n\" +\n\t\t\t\t\t\t\"**Execution order**: \" +\n\t\t\t\t\t\t\"1) Identify the test files to run from test-report.md. \" +\n\t\t\t\t\t\t\"2) Run the full test suite ONCE with a single command. \" +\n\t\t\t\t\t\t\"3) Run CI verification commands (type-check, lint) ONCE at the end. \" +\n\t\t\t\t\t\t\"Do not run tests file-by-file — use a single test runner invocation.\";\n\n\t\tcase \"review\":\n\t\t\treturn baseBranch\n\t\t\t\t? `Review the implementation for correctness, quality, architecture compliance, and standards compliance. Produce a review.md with your verdict.\\n\\n` +\n\t\t\t\t\t\t`**Start by running \\`git diff ${baseBranch}..HEAD\\`** to see all changes at once. ` +\n\t\t\t\t\t\t`Use \\`git diff --stat ${baseBranch}..HEAD\\` for an overview.`\n\t\t\t\t: \"Review the implementation for correctness, quality, architecture compliance, and standards compliance. Produce a review.md with your verdict.\";\n\n\t\tcase \"branch\":\n\t\t\treturn baseBranch\n\t\t\t\t? `Create a feature branch from \\`${baseBranch}\\`. Run \\`git checkout ${baseBranch}\\` first, then create the branch.`\n\t\t\t\t: `Create a feature branch for ${agent === \"cicd\" ? \"this feature\" : \"the implementation\"}.`;\n\n\t\tcase \"merge\":\n\t\t\treturn baseBranch\n\t\t\t\t? `Merge the feature branch into \\`${baseBranch}\\` using \\`--no-ff\\`.\\n\\n` +\n\t\t\t\t\t\t`**Start by running \\`git diff ${baseBranch}..HEAD --name-only\\`** to get the complete list of changed files. ` +\n\t\t\t\t\t\t`Use this list for \\`git add\\` in a single command — do NOT use \\`ls\\`, \\`find\\`, or \\`git status\\` to discover files.\\n\\n` +\n\t\t\t\t\t\t`Run \\`git checkout ${baseBranch}\\` first, then merge. Do NOT run \\`git push\\` — push is handled by the orchestration system.`\n\t\t\t\t: \"Merge the feature branch after all checks pass. Do NOT run `git push`.\";\n\n\t\tcase \"analyze\":\n\t\t\treturn \"Analyze the impact of the proposed change on the existing codebase. Produce an impact-report.md.\";\n\n\t\tcase \"investigate\":\n\t\t\treturn \"Trace through the code to identify the root cause of the issue. Use read and bash tools to explore the codebase, run tests, add debug output if needed. Do not produce any artifacts — this is an exploratory step. Your findings will inform the subsequent diagnosis step.\";\n\n\t\tcase \"diagnose\":\n\t\t\treturn \"Analyze the bug, reproduce it, identify the root cause, and produce a diagnosis.md.\";\n\n\t\tdefault:\n\t\t\treturn `Execute the \"${action}\" task for this feature.`;\n\t}\n}\n\n// ─── Default Step Executor ───────────────────────────────────────────────────\n\n/**\n * Default StepExecutor implementation.\n * Creates a role-specific agent, passes input artifacts as context,\n * runs the agent, and saves the output artifacts.\n */\nexport const defaultStepExecutor: StepExecutor = async (step, featureId, context) => {\n\tconst { store, config, projectRoot } = context;\n\tconst log = createModuleLogger(context.logger ?? noopLogger, \"step-executor\");\n\n\t// 1. Ensure feature directory exists\n\tawait store.ensureFeatureDir(featureId);\n\n\t// 2. Load input artifacts\n\tlog.debug(`Loading input artifacts: [${step.inputs.join(\", \")}]`, { featureId, agent: step.agent });\n\tlet inputContent = await loadInputArtifacts(step.inputs, featureId, context);\n\n\t// 2b. Inject system-design.md for roles that need system-level architecture context\n\tif (SYSTEM_DESIGN_ROLES.has(step.agent) && (await store.hasSystemDesign())) {\n\t\tconst systemDesign = await store.readSystemDesign();\n\t\tconst systemDesignSection = `## Input: system-design.md (System Architecture)\\n\\n${systemDesign}`;\n\t\tinputContent = inputContent ? `${inputContent}\\n\\n---\\n\\n${systemDesignSection}` : systemDesignSection;\n\t\tlog.debug(`Injected system-design.md into ${step.agent} context`, { featureId });\n\t}\n\n\t// 3. Get system prompt\n\tconst systemPrompt = getSystemPromptForRole(step.agent);\n\n\t// 4. Artifact directory path\n\tconst artifactDir = store.getFeatureDir(featureId);\n\n\t// 4b. Lightweight test config detection for new projects\n\t// When config.testing.testCommand is empty (project-analyze ran before project existed),\n\t// detect from package.json and config files so the tester has concrete commands to use.\n\tlet effectiveTestingConfig = config.testing;\n\tif (\n\t\tstep.agent === \"tester\" &&\n\t\t(step.action === \"test\" || step.action === \"regression\") &&\n\t\t!config.testing?.testCommand\n\t) {\n\t\tconst detected = await detectTestConfig(projectRoot);\n\t\tif (detected.testCommand || detected.ciChecks.length > 0) {\n\t\t\teffectiveTestingConfig = {\n\t\t\t\ttestCommand: detected.testCommand ?? \"\",\n\t\t\t\ttestTimeout: config.testing?.testTimeout ?? 300,\n\t\t\t\trunExistingTests: config.testing?.runExistingTests ?? false,\n\t\t\t\texcludePatterns: config.testing?.excludePatterns ?? [],\n\t\t\t\tciChecks: [...(config.testing?.ciChecks ?? []), ...detected.ciChecks],\n\t\t\t};\n\t\t\tlog.debug(\n\t\t\t\t`Detected test config for new project: testCommand=${detected.testCommand ?? \"(none)\"}, ciChecks=${detected.ciChecks.length}`,\n\t\t\t\t{ featureId },\n\t\t\t);\n\t\t}\n\t}\n\n\t// 5. Build execution prompt\n\tlet actionPrompt = buildActionPrompt(\n\t\tstep,\n\t\tfeatureId,\n\t\tinputContent,\n\t\tartifactDir,\n\t\tconfig.baseBranch,\n\t\teffectiveTestingConfig,\n\t\tprojectRoot,\n\t);\n\n\t// 6a. Load prior action history for the same role (only when step opts in)\n\tlet initialMessages: import(\"@mariozechner/pi-agent-core\").AgentMessage[] | undefined;\n\tif (step.inheritPriorHistory) {\n\t\tconst priorCheckpoints = await store.loadPriorAgentHistory(featureId, step.agent, step.action);\n\t\tif (priorCheckpoints.length > 0) {\n\t\t\tlog.debug(`Loading prior action history for ${step.agent}: ${priorCheckpoints.length} checkpoint(s)`, {\n\t\t\t\tfeatureId,\n\t\t\t\tagent: step.agent,\n\t\t\t});\n\t\t\tinitialMessages = priorCheckpoints.flatMap((cp) => cp.messages);\n\t\t}\n\t}\n\n\t// 6b. If retry history exists for the current action, append it (checkpoint restoration on retry)\n\tconst hasHistory = await store.hasAgentHistory(featureId, step.agent, step.action);\n\tif (hasHistory) {\n\t\tconst checkpoint = await store.loadAgentHistory(featureId, step.agent, step.action);\n\t\tif (checkpoint) {\n\t\t\tinitialMessages = [...(initialMessages ?? []), ...checkpoint.messages];\n\t\t\tactionPrompt = `[RETRY] Previous attempt for this step exists in your conversation history. Review what went wrong and try a different approach.\\n\\n${actionPrompt}`;\n\t\t}\n\t}\n\n\t// 7. Create agent (inject prior history)\n\tlog.info(`Creating agent: ${step.agent} for action: ${step.action}`, { featureId, hasHistory: !!initialMessages });\n\tconst agent = await createRoleAgent({\n\t\trole: step.agent,\n\t\tsystemPrompt,\n\t\tconfig,\n\t\tprojectRoot,\n\t\tmodel: context.model,\n\t\tfeatureContext: inputContent || undefined,\n\t\tgetApiKey: context.getApiKey,\n\t\tinitialMessages,\n\t});\n\n\t// 8. Agent registration callback\n\tcontext.onAgentCreated?.(agent);\n\n\ttry {\n\t\t// 9. Run agent (with history)\n\t\tlog.info(`Agent executing: ${step.agent}:${step.action}`, { featureId });\n\t\tconst result = await runAgentWithHistory(agent, actionPrompt);\n\n\t\t// 9b. Aggregate token usage and capture response text\n\t\tcontext.lastStepUsage = aggregateUsage(result.messages);\n\t\tcontext.lastStepResponseText = result.text;\n\n\t\t// 10. Save history checkpoint\n\t\tawait store.saveAgentHistory(featureId, {\n\t\t\trole: step.agent,\n\t\t\taction: step.action,\n\t\t\tmessages: filterSerializableMessages(result.messages),\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t});\n\n\t\t// 11. Save artifacts for readonly roles\n\t\tif (isReadonlyRole(step.agent) && step.outputs.length > 0) {\n\t\t\tconst isSingle = step.outputs.length === 1;\n\t\t\tfor (const output of step.outputs) {\n\t\t\t\tconst content = extractArtifactContent(result.text, output, isSingle);\n\t\t\t\tif (content) {\n\t\t\t\t\tlog.debug(`Extracted artifact: ${output}`, { featureId, agent: step.agent });\n\t\t\t\t\tawait store.writeArtifact(featureId, output as ArtifactName, content);\n\t\t\t\t} else {\n\t\t\t\t\tlog.warn(`Failed to extract artifact: ${output}`, { featureId, agent: step.agent });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tlog.info(`Agent completed: ${step.agent}:${step.action}`, { featureId });\n\t} finally {\n\t\t// 12. Agent release callback\n\t\tcontext.onAgentFinished?.();\n\t}\n};\n"]}
package/dist/store.d.ts CHANGED
@@ -154,16 +154,6 @@ export declare class VibeStore {
154
154
  loadPipelineState(featureId: string): Promise<PersistedPipeline | null>;
155
155
  /** Deletes the pipeline state file. */
156
156
  clearPipelineState(featureId: string): Promise<void>;
157
- /** Returns the path to .vibe/skills/ directory. */
158
- getSkillsDir(): string;
159
- /** Returns a list of directories under .vibe/skills/ that contain SKILL.md. */
160
- listSkills(): Promise<string[]>;
161
- /** Checks if a specific vibe skill exists. */
162
- hasSkill(skillName: string): Promise<boolean>;
163
- /** Reads the full contents of a vibe skill's SKILL.md. */
164
- readSkillContent(skillName: string): Promise<string>;
165
- /** Extracts only the description from a vibe skill's SKILL.md frontmatter. Returns null if not found. */
166
- readSkillDescription(skillName: string): Promise<string | null>;
167
157
  /** Deletes the entire .vibe/ directory and re-initializes from scratch. */
168
158
  reset(): Promise<void>;
169
159
  private ensureRootGitignore;
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { access, mkdir, readdir, readFile, rm, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { createModuleLogger, type ModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type {\n\tAgentCheckpoint,\n\tArtifactName,\n\tFeatureStatus,\n\tFeatureStatusValue,\n\tOrchestrationState,\n\tPersistedPipeline,\n\tPlanData,\n\tVibeConfig,\n} from \"./types.js\";\n\n/** Valid state transition map. */\nconst VALID_TRANSITIONS: Record<FeatureStatusValue, FeatureStatusValue[]> = {\n\tplanned: [\"designing\", \"blocked\", \"failed\"],\n\tdesigning: [\"branching\", \"implementing\", \"blocked\", \"failed\"],\n\tbranching: [\"implementing\", \"blocked\", \"failed\"],\n\timplementing: [\"testing\", \"done\", \"blocked\", \"failed\"],\n\ttesting: [\"reviewing\", \"implementing\", \"blocked\", \"failed\"],\n\treviewing: [\"done\", \"merging\", \"implementing\", \"blocked\", \"failed\"],\n\tmerging: [\"done\", \"blocked\", \"failed\"],\n\tdone: [],\n\tblocked: [\"planned\", \"designing\", \"branching\", \"implementing\", \"testing\", \"reviewing\", \"merging\", \"failed\"],\n\tfailed: [],\n};\n\n/** Default configuration. */\nconst DEFAULT_CONFIG: VibeConfig = {\n\tautonomyLevel: \"gate_auto\",\n\tgates: {\n\t\trequireRequirementsApproval: true,\n\t\trequirePlanApproval: true,\n\t\trequireDesignApproval: false,\n\t\trequireSystemDesignApproval: true,\n\t\trequireMergeApproval: true,\n\t\trequireDiagnosisApproval: true,\n\t},\n\tretry: {\n\t\tmaxTestRetries: 3,\n\t\tmaxReviewRetries: 2,\n\t\tmaxRegressionRetries: 3,\n\t\tmaxStepRetries: 2,\n\t},\n\tstandards: {\n\t\tsources: [\".vibe/standards/\"],\n\t},\n\tnotifications: {\n\t\tonStepComplete: true,\n\t\tonBlocked: true,\n\t\tonPRReady: true,\n\t\tchannel: \"terminal\",\n\t},\n\tprojectAnalysis: {\n\t\tautoAnalyzeOnInit: true,\n\t\tstaleThresholdDays: 7,\n\t\texcludePaths: [\"node_modules\", \"dist\", \".git\"],\n\t},\n\tdiscovery: {\n\t\tmaxQuestionRounds: 5,\n\t\tskipIfClear: true,\n\t},\n\tcompaction: {\n\t\tenabled: true,\n\t\treserveTokens: 16384,\n\t\tkeepRecentTokens: 20000,\n\t},\n\ttesting: {\n\t\ttestCommand: \"\",\n\t\ttestTimeout: 300,\n\t\trunExistingTests: false,\n\t\texcludePatterns: [],\n\t\tciChecks: [],\n\t},\n};\n\n/** Marker comment used to identify vibe-managed gitignore block. */\nconst VIBE_GITIGNORE_MARKER = \"# >>> vibe managed\";\nconst VIBE_GITIGNORE_MARKER_END = \"# <<< vibe managed\";\n\n/** Gitignore rules appended to project root .gitignore. */\nconst VIBE_GITIGNORE_BLOCK = `${VIBE_GITIGNORE_MARKER}\n.vibe/logs/\n.vibe/orchestration-state.json\n.vibe/requirements.md\n.vibe/project-context.md\n.vibe/agent-history/\n.vibe/features/*/agent-history/\n${VIBE_GITIGNORE_MARKER_END}`;\n\n/** Deep merges two objects. Keys missing from target are taken from defaults. */\nfunction deepMerge(target: Record<string, unknown>, defaults: Record<string, unknown>): Record<string, unknown> {\n\tconst result = { ...defaults };\n\tfor (const key of Object.keys(target)) {\n\t\tconst targetVal = target[key];\n\t\tconst defaultVal = defaults[key];\n\t\tif (\n\t\t\ttargetVal !== undefined &&\n\t\t\ttargetVal !== null &&\n\t\t\ttypeof targetVal === \"object\" &&\n\t\t\t!Array.isArray(targetVal) &&\n\t\t\ttypeof defaultVal === \"object\" &&\n\t\t\tdefaultVal !== null &&\n\t\t\t!Array.isArray(defaultVal)\n\t\t) {\n\t\t\tresult[key] = deepMerge(targetVal as Record<string, unknown>, defaultVal as Record<string, unknown>);\n\t\t} else if (targetVal !== undefined) {\n\t\t\tresult[key] = targetVal;\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Manages .vibe/ directory creation, feature state machine, project config loading,\n * and standard path resolution.\n */\nexport class VibeStore {\n\tprivate readonly vibeDir: string;\n\tprivate readonly featuresDir: string;\n\tprivate readonly standardsDir: string;\n\tprivate readonly log: ModuleLogger;\n\n\tconstructor(\n\t\tprivate readonly projectRoot: string,\n\t\tlogger?: VibeLogger,\n\t) {\n\t\tthis.vibeDir = join(projectRoot, \".vibe\");\n\t\tthis.featuresDir = join(this.vibeDir, \"features\");\n\t\tthis.standardsDir = join(this.vibeDir, \"standards\");\n\t\tthis.log = createModuleLogger(logger ?? noopLogger, \"store\");\n\t}\n\n\t/** Returns the project root path. */\n\tgetProjectRoot(): string {\n\t\treturn this.projectRoot;\n\t}\n\n\t// ─── Directory ───────────────────────────────────────────────────────────\n\n\t/** Initializes the .vibe/ directory structure. */\n\tasync init(): Promise<void> {\n\t\tawait mkdir(this.featuresDir, { recursive: true });\n\t\tawait mkdir(this.standardsDir, { recursive: true });\n\t\tawait mkdir(this.getSkillsDir(), { recursive: true });\n\n\t\tconst configPath = join(this.vibeDir, \"config.json\");\n\t\tif (!(await this.fileExists(configPath))) {\n\t\t\tawait writeFile(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, \"\\t\")}\\n`, \"utf-8\");\n\t\t}\n\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tif (!(await this.fileExists(planPath))) {\n\t\t\tconst emptyPlan: PlanData = {\n\t\t\t\tproject: \"\",\n\t\t\t\tdescription: \"\",\n\t\t\t\tworkflowType: \"new_feature\",\n\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t\tfeatures: [],\n\t\t\t\tdependencyGraph: {},\n\t\t\t};\n\t\t\tawait writeFile(planPath, `${JSON.stringify(emptyPlan, null, \"\\t\")}\\n`, \"utf-8\");\n\t\t}\n\n\t\tawait this.ensureRootGitignore();\n\t}\n\n\t/** Check if .vibe/ directory has been initialized. */\n\tasync isInitialized(): Promise<boolean> {\n\t\treturn this.fileExists(this.vibeDir);\n\t}\n\n\tgetVibeDir(): string {\n\t\treturn this.vibeDir;\n\t}\n\n\tgetFeaturesDir(): string {\n\t\treturn this.featuresDir;\n\t}\n\n\tgetStandardsDir(): string {\n\t\treturn this.standardsDir;\n\t}\n\n\tgetFeatureDir(featureId: string): string {\n\t\treturn join(this.featuresDir, featureId);\n\t}\n\n\t/** Lists all feature IDs (directory names under features/). */\n\tasync listFeatures(): Promise<string[]> {\n\t\tif (!(await this.fileExists(this.featuresDir))) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(this.featuresDir, { withFileTypes: true });\n\t\treturn entries\n\t\t\t.filter((e) => e.isDirectory())\n\t\t\t.map((e) => e.name)\n\t\t\t.sort();\n\t}\n\n\t/** Creates the feature directory if it doesn't exist and returns the path. */\n\tasync ensureFeatureDir(featureId: string): Promise<string> {\n\t\tconst dir = this.getFeatureDir(featureId);\n\t\tawait mkdir(dir, { recursive: true });\n\t\treturn dir;\n\t}\n\n\t// ─── Config ──────────────────────────────────────────────────────────────\n\n\t/** Reads config.json and returns a complete VibeConfig merged with defaults. */\n\tasync loadConfig(): Promise<VibeConfig> {\n\t\tconst configPath = join(this.vibeDir, \"config.json\");\n\t\tif (!(await this.fileExists(configPath))) {\n\t\t\treturn { ...DEFAULT_CONFIG };\n\t\t}\n\t\tconst raw = await readFile(configPath, \"utf-8\");\n\t\tconst partial = JSON.parse(raw) as Record<string, unknown>;\n\t\treturn deepMerge(partial, DEFAULT_CONFIG as unknown as Record<string, unknown>) as unknown as VibeConfig;\n\t}\n\n\t/** Saves configuration to config.json. */\n\tasync saveConfig(config: VibeConfig): Promise<void> {\n\t\tconst configPath = join(this.vibeDir, \"config.json\");\n\t\tawait writeFile(configPath, `${JSON.stringify(config, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\t// ─── Plan ────────────────────────────────────────────────────────────────\n\n\t/** Reads plan.json. */\n\t/** Checks if a meaningful plan exists. Returns true if plan.json exists and has at least one feature. */\n\tasync hasPlan(): Promise<boolean> {\n\t\ttry {\n\t\t\tconst raw = await readFile(join(this.vibeDir, \"plan.json\"), \"utf-8\");\n\t\t\tconst plan = JSON.parse(raw) as PlanData;\n\t\t\treturn plan.features.length > 0;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync loadPlan(): Promise<PlanData> {\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tconst raw = await readFile(planPath, \"utf-8\");\n\t\treturn JSON.parse(raw) as PlanData;\n\t}\n\n\t/** Writes plan.json. */\n\tasync savePlan(plan: PlanData): Promise<void> {\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tawait writeFile(planPath, `${JSON.stringify(plan, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\t// ─── Feature Status ──────────────────────────────────────────────────────\n\n\t/** Reads the feature's status.json. */\n\tasync loadFeatureStatus(featureId: string): Promise<FeatureStatus> {\n\t\tconst statusPath = join(this.getFeatureDir(featureId), \"status.json\");\n\t\tconst raw = await readFile(statusPath, \"utf-8\");\n\t\treturn JSON.parse(raw) as FeatureStatus;\n\t}\n\n\t/**\n\t * Updates feature status. Validates state transitions and syncs status.json with plan.json.\n\t * Auto-creates the feature directory and status.json if they don't exist.\n\t */\n\tasync updateFeatureStatus(featureId: string, newStatus: FeatureStatusValue): Promise<void> {\n\t\tthis.log.debug(`Updating feature status: ${featureId} → ${newStatus}`, { featureId, newStatus });\n\t\tawait this.ensureFeatureDir(featureId);\n\t\tconst statusPath = join(this.getFeatureDir(featureId), \"status.json\");\n\n\t\tlet currentStatus: FeatureStatus;\n\t\tif (await this.fileExists(statusPath)) {\n\t\t\tconst raw = await readFile(statusPath, \"utf-8\");\n\t\t\tcurrentStatus = JSON.parse(raw) as FeatureStatus;\n\n\t\t\t// Idempotent: same-state transition is a no-op\n\t\t\tif (currentStatus.status === newStatus) {\n\t\t\t\tthis.log.debug(`Status already \"${newStatus}\", skipping transition`, { featureId });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst allowed = VALID_TRANSITIONS[currentStatus.status];\n\t\t\tif (!allowed.includes(newStatus)) {\n\t\t\t\tthis.log.warn(`Invalid state transition: \"${currentStatus.status}\" → \"${newStatus}\"`, { featureId });\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid state transition: \"${currentStatus.status}\" → \"${newStatus}\" for feature \"${featureId}\". Allowed transitions: [${allowed.join(\", \")}]`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tcurrentStatus = {\n\t\t\t\tstatus: \"planned\",\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\t// New features can only start from planned. Validate planned → newStatus transition.\n\t\t\tif (newStatus !== \"planned\") {\n\t\t\t\tconst allowed = VALID_TRANSITIONS.planned;\n\t\t\t\tif (!allowed.includes(newStatus)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Invalid state transition: \"planned\" → \"${newStatus}\" for new feature \"${featureId}\". Allowed transitions: [${allowed.join(\", \")}]`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst updated: FeatureStatus = {\n\t\t\tstatus: newStatus,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t};\n\t\tawait writeFile(statusPath, `${JSON.stringify(updated, null, \"\\t\")}\\n`, \"utf-8\");\n\n\t\t// Sync with plan.json\n\t\tawait this.syncPlanFeatureStatus(featureId, newStatus);\n\t}\n\n\t/** Syncs the feature's status within plan.json. */\n\tprivate async syncPlanFeatureStatus(featureId: string, status: FeatureStatusValue): Promise<void> {\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tif (!(await this.fileExists(planPath))) {\n\t\t\treturn;\n\t\t}\n\t\tconst plan = await this.loadPlan();\n\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\tif (feature) {\n\t\t\tfeature.status = status;\n\t\t\tplan.updatedAt = new Date().toISOString();\n\t\t\tawait this.savePlan(plan);\n\t\t}\n\t}\n\n\t// ─── Artifacts ───────────────────────────────────────────────────────────\n\n\t/** Returns the absolute path for an artifact file. */\n\tgetArtifactPath(featureId: string, artifact: ArtifactName): string {\n\t\treturn join(this.getFeatureDir(featureId), artifact);\n\t}\n\n\t/** Checks if an artifact file exists. */\n\tasync hasArtifact(featureId: string, artifact: ArtifactName): Promise<boolean> {\n\t\treturn this.fileExists(this.getArtifactPath(featureId, artifact));\n\t}\n\n\t/** Reads an artifact file. */\n\tasync readArtifact(featureId: string, artifact: ArtifactName): Promise<string> {\n\t\treturn readFile(this.getArtifactPath(featureId, artifact), \"utf-8\");\n\t}\n\n\t/** Writes an artifact file. Auto-creates the feature directory if missing. */\n\tasync writeArtifact(featureId: string, artifact: ArtifactName, content: string): Promise<void> {\n\t\tthis.log.debug(`Writing artifact: ${artifact}`, { featureId });\n\t\tawait this.ensureFeatureDir(featureId);\n\t\tawait writeFile(this.getArtifactPath(featureId, artifact), content, \"utf-8\");\n\t}\n\n\t/** Writes a global artifact file to the .vibe/ root. */\n\tasync writeGlobalArtifact(filename: string, content: string): Promise<void> {\n\t\tawait writeFile(join(this.vibeDir, filename), content, \"utf-8\");\n\t}\n\n\t/** Reads a global artifact file from the .vibe/ root. */\n\tasync readGlobalArtifact(filename: string): Promise<string> {\n\t\treturn readFile(join(this.vibeDir, filename), \"utf-8\");\n\t}\n\n\t/** Checks if a global artifact file exists. */\n\tasync hasGlobalArtifact(filename: string): Promise<boolean> {\n\t\treturn this.fileExists(join(this.vibeDir, filename));\n\t}\n\n\t// ─── Standards ───────────────────────────────────────────────────────────\n\n\t/** Returns a list of .md files in .vibe/standards/ (filenames only). */\n\tasync listStandardFiles(): Promise<string[]> {\n\t\tif (!(await this.fileExists(this.standardsDir))) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(this.standardsDir);\n\t\treturn entries.filter((e) => e.endsWith(\".md\")).sort();\n\t}\n\n\t/** Resolves a standards source path to an absolute path. */\n\tresolveStandardSource(source: string): string {\n\t\tif (isAbsolute(source)) {\n\t\t\treturn source;\n\t\t}\n\t\treturn resolve(this.projectRoot, source);\n\t}\n\n\t/** Checks if a standard file exists in .vibe/standards/. */\n\tasync hasStandardFile(fileName: string): Promise<boolean> {\n\t\treturn this.fileExists(join(this.standardsDir, fileName));\n\t}\n\n\t/** Reads all standard files and returns them as Map<filename, content>. */\n\tasync readAllStandards(): Promise<Map<string, string>> {\n\t\tconst files = await this.listStandardFiles();\n\t\tconst result = new Map<string, string>();\n\t\tfor (const fileName of files) {\n\t\t\tconst content = await readFile(join(this.standardsDir, fileName), \"utf-8\");\n\t\t\tresult.set(fileName, content);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/** Writes a single standard file. */\n\tasync writeStandard(fileName: string, content: string): Promise<void> {\n\t\tawait writeFile(join(this.standardsDir, fileName), content, \"utf-8\");\n\t}\n\n\t// ─── Requirements ────────────────────────────────────────────────────────\n\n\t/** Returns the path to .vibe/requirements.md. */\n\tgetRequirementsPath(): string {\n\t\treturn join(this.vibeDir, \"requirements.md\");\n\t}\n\n\t/** Checks if requirements.md exists. */\n\tasync hasRequirements(): Promise<boolean> {\n\t\treturn this.fileExists(this.getRequirementsPath());\n\t}\n\n\t/** Reads requirements.md. */\n\tasync readRequirements(): Promise<string> {\n\t\treturn readFile(this.getRequirementsPath(), \"utf-8\");\n\t}\n\n\t/** Writes requirements.md. */\n\tasync writeRequirements(content: string): Promise<void> {\n\t\tawait writeFile(this.getRequirementsPath(), content, \"utf-8\");\n\t}\n\n\t/** Deletes requirements.md. */\n\tasync clearRequirements(): Promise<void> {\n\t\tconst filePath = this.getRequirementsPath();\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t// ─── System Design ───────────────────────────────────────────────────────\n\n\t/** Returns the path to .vibe/system-design.md. */\n\tgetSystemDesignPath(): string {\n\t\treturn join(this.vibeDir, \"system-design.md\");\n\t}\n\n\t/** Checks if system-design.md exists. */\n\tasync hasSystemDesign(): Promise<boolean> {\n\t\treturn this.fileExists(this.getSystemDesignPath());\n\t}\n\n\t/** Reads system-design.md. */\n\tasync readSystemDesign(): Promise<string> {\n\t\treturn readFile(this.getSystemDesignPath(), \"utf-8\");\n\t}\n\n\t/** Writes system-design.md. */\n\tasync writeSystemDesign(content: string): Promise<void> {\n\t\tawait writeFile(this.getSystemDesignPath(), content, \"utf-8\");\n\t}\n\n\t/** Deletes system-design.md. */\n\tasync clearSystemDesign(): Promise<void> {\n\t\tconst filePath = this.getSystemDesignPath();\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t/** Deletes plan.json. */\n\tasync clearPlan(): Promise<void> {\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tif (await this.fileExists(planPath)) {\n\t\t\tawait unlink(planPath);\n\t\t}\n\t}\n\n\t/** Recursively deletes the entire feature directory. */\n\tasync clearFeatureDir(featureId: string): Promise<void> {\n\t\tconst dir = this.getFeatureDir(featureId);\n\t\tif (await this.fileExists(dir)) {\n\t\t\tawait rm(dir, { recursive: true, force: true });\n\t\t}\n\t}\n\n\t/** Deletes all feature directories under features/. */\n\tasync clearAllFeatures(): Promise<void> {\n\t\tconst dir = this.getFeaturesDir();\n\t\tif (!(await this.fileExists(dir))) return;\n\t\tconst entries = await readdir(dir);\n\t\tfor (const entry of entries) {\n\t\t\tawait rm(join(dir, entry), { recursive: true, force: true });\n\t\t}\n\t}\n\n\t/** Deletes global agent history by role name. (e.g., \"discovery\" → discovery-*.json) */\n\tasync clearGlobalAgentHistoryByRole(role: string): Promise<void> {\n\t\tconst dir = this.getGlobalAgentHistoryDir();\n\t\tif (!(await this.fileExists(dir))) return;\n\t\tconst entries = await readdir(dir);\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.startsWith(`${role}-`) && entry.endsWith(\".json\")) {\n\t\t\t\tawait unlink(join(dir, entry));\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Deletes a global artifact. */\n\tasync clearGlobalArtifact(filename: string): Promise<void> {\n\t\tconst filePath = join(this.vibeDir, filename);\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t// ─── Project Context ─────────────────────────────────────────────────────\n\n\t/** Returns the path to .vibe/project-context.md. */\n\tgetProjectContextPath(): string {\n\t\treturn join(this.vibeDir, \"project-context.md\");\n\t}\n\n\t/** Checks if project-context.md exists. */\n\tasync hasProjectContext(): Promise<boolean> {\n\t\treturn this.fileExists(this.getProjectContextPath());\n\t}\n\n\t/** Reads project-context.md. */\n\tasync readProjectContext(): Promise<string> {\n\t\treturn readFile(this.getProjectContextPath(), \"utf-8\");\n\t}\n\n\t/** Writes project-context.md. */\n\tasync writeProjectContext(content: string): Promise<void> {\n\t\tawait writeFile(this.getProjectContextPath(), content, \"utf-8\");\n\t}\n\n\t/** Checks if project-context.md is older than staleThresholdDays. */\n\tasync isProjectContextStale(thresholdDays: number): Promise<boolean> {\n\t\tconst contextPath = this.getProjectContextPath();\n\t\tif (!(await this.fileExists(contextPath))) {\n\t\t\treturn true;\n\t\t}\n\t\tconst fileStat = await stat(contextPath);\n\t\tconst ageMs = Date.now() - fileStat.mtimeMs;\n\t\tconst ageDays = ageMs / (1000 * 60 * 60 * 24);\n\t\treturn ageDays > thresholdDays;\n\t}\n\n\t// ─── Agent History (Feature Level) ───────────────────────────────────────\n\n\t/** Returns the agent history directory path for a feature. */\n\tgetAgentHistoryDir(featureId: string): string {\n\t\treturn join(this.getFeatureDir(featureId), \"agent-history\");\n\t}\n\n\t/** Returns the agent history file path. */\n\tgetAgentHistoryPath(featureId: string, role: string, action: string): string {\n\t\treturn join(this.getAgentHistoryDir(featureId), `${role}-${action}.json`);\n\t}\n\n\t/** Saves agent history. Auto-creates the directory if missing. */\n\tasync saveAgentHistory(featureId: string, checkpoint: AgentCheckpoint): Promise<void> {\n\t\tthis.log.debug(`Saving agent history: ${checkpoint.role}:${checkpoint.action}`, { featureId });\n\t\tconst dir = this.getAgentHistoryDir(featureId);\n\t\tawait mkdir(dir, { recursive: true });\n\t\tconst filePath = this.getAgentHistoryPath(featureId, checkpoint.role, checkpoint.action);\n\t\tawait writeFile(filePath, JSON.stringify(checkpoint, null, \"\\t\"), \"utf-8\");\n\t}\n\n\t/** Loads agent history. Returns null if not found. */\n\tasync loadAgentHistory(featureId: string, role: string, action: string): Promise<AgentCheckpoint | null> {\n\t\tconst filePath = this.getAgentHistoryPath(featureId, role, action);\n\t\tif (!(await this.fileExists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\treturn JSON.parse(raw) as AgentCheckpoint;\n\t}\n\n\t/** Checks if agent history exists. */\n\tasync hasAgentHistory(featureId: string, role: string, action: string): Promise<boolean> {\n\t\treturn this.fileExists(this.getAgentHistoryPath(featureId, role, action));\n\t}\n\n\t/**\n\t * Loads prior action histories for the same role in chronological order.\n\t * Excludes the currentAction's own history (separated from retry checkpointing).\n\t */\n\tasync loadPriorAgentHistory(featureId: string, role: string, currentAction: string): Promise<AgentCheckpoint[]> {\n\t\tconst dir = this.getAgentHistoryDir(featureId);\n\t\tif (!(await this.fileExists(dir))) return [];\n\n\t\tconst entries = await readdir(dir);\n\t\tconst prefix = `${role}-`;\n\t\tconst checkpoints: AgentCheckpoint[] = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tif (!entry.startsWith(prefix) || !entry.endsWith(\".json\")) continue;\n\t\t\tconst action = entry.slice(prefix.length, -5);\n\t\t\tif (action === currentAction) continue;\n\n\t\t\tconst raw = await readFile(join(dir, entry), \"utf-8\");\n\t\t\tcheckpoints.push(JSON.parse(raw) as AgentCheckpoint);\n\t\t}\n\n\t\treturn checkpoints.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\t}\n\n\t/** Returns all agent history filenames for a feature. */\n\tasync listAgentHistory(featureId: string): Promise<string[]> {\n\t\tconst dir = this.getAgentHistoryDir(featureId);\n\t\tif (!(await this.fileExists(dir))) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(dir);\n\t\treturn entries.filter((e) => e.endsWith(\".json\")).sort();\n\t}\n\n\t// ─── Agent History (Global Level) ────────────────────────────────────────\n\n\t/** Returns the project-level agent history directory path. */\n\tgetGlobalAgentHistoryDir(): string {\n\t\treturn join(this.vibeDir, \"agent-history\");\n\t}\n\n\t/** Returns the project-level agent history file path. */\n\tgetGlobalAgentHistoryPath(role: string, action: string): string {\n\t\treturn join(this.getGlobalAgentHistoryDir(), `${role}-${action}.json`);\n\t}\n\n\t/** Saves project-level agent history. */\n\tasync saveGlobalAgentHistory(checkpoint: AgentCheckpoint): Promise<void> {\n\t\tconst dir = this.getGlobalAgentHistoryDir();\n\t\tawait mkdir(dir, { recursive: true });\n\t\tconst filePath = this.getGlobalAgentHistoryPath(checkpoint.role, checkpoint.action);\n\t\tawait writeFile(filePath, JSON.stringify(checkpoint, null, \"\\t\"), \"utf-8\");\n\t}\n\n\t/** Loads project-level agent history. Returns null if not found. */\n\tasync loadGlobalAgentHistory(role: string, action: string): Promise<AgentCheckpoint | null> {\n\t\tconst filePath = this.getGlobalAgentHistoryPath(role, action);\n\t\tif (!(await this.fileExists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\treturn JSON.parse(raw) as AgentCheckpoint;\n\t}\n\n\t/** Checks if project-level agent history exists. */\n\tasync hasGlobalAgentHistory(role: string, action: string): Promise<boolean> {\n\t\treturn this.fileExists(this.getGlobalAgentHistoryPath(role, action));\n\t}\n\n\t/** Returns a list of project-level agent history files. */\n\tasync listGlobalAgentHistory(): Promise<string[]> {\n\t\tconst dir = this.getGlobalAgentHistoryDir();\n\t\tif (!(await this.fileExists(dir))) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(dir);\n\t\treturn entries.filter((e) => e.endsWith(\".json\")).sort();\n\t}\n\n\t// ─── Orchestration State Persistence ─────────────────────────────────────\n\n\t/** Returns the path to orchestration-state.json. */\n\tgetOrchestrationStatePath(): string {\n\t\treturn join(this.vibeDir, \"orchestration-state.json\");\n\t}\n\n\t/** Saves orchestration state. Auto-creates .vibe/ directory if missing. */\n\tasync saveOrchestrationState(state: OrchestrationState): Promise<void> {\n\t\tawait mkdir(this.vibeDir, { recursive: true });\n\t\tawait writeFile(this.getOrchestrationStatePath(), `${JSON.stringify(state, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\t/** Loads orchestration state. Returns null if not found. */\n\tasync loadOrchestrationState(): Promise<OrchestrationState | null> {\n\t\tconst filePath = this.getOrchestrationStatePath();\n\t\tif (!(await this.fileExists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\treturn JSON.parse(raw) as OrchestrationState;\n\t}\n\n\t/** Deletes the orchestration state file. */\n\tasync clearOrchestrationState(): Promise<void> {\n\t\tconst filePath = this.getOrchestrationStatePath();\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t/** Checks if orchestration state file exists. */\n\tasync hasOrchestrationState(): Promise<boolean> {\n\t\treturn this.fileExists(this.getOrchestrationStatePath());\n\t}\n\n\t// ─── Pipeline State Persistence ──────────────────────────────────────────\n\n\t/** Returns the path to pipeline.json. */\n\tgetPipelineStatePath(featureId: string): string {\n\t\treturn join(this.getFeatureDir(featureId), \"pipeline.json\");\n\t}\n\n\t/** Saves pipeline state. */\n\tasync savePipelineState(featureId: string, state: PersistedPipeline): Promise<void> {\n\t\tawait this.ensureFeatureDir(featureId);\n\t\tawait writeFile(this.getPipelineStatePath(featureId), `${JSON.stringify(state, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\t/** Loads pipeline state. Returns null if not found. */\n\tasync loadPipelineState(featureId: string): Promise<PersistedPipeline | null> {\n\t\tconst filePath = this.getPipelineStatePath(featureId);\n\t\tif (!(await this.fileExists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\treturn JSON.parse(raw) as PersistedPipeline;\n\t}\n\n\t/** Deletes the pipeline state file. */\n\tasync clearPipelineState(featureId: string): Promise<void> {\n\t\tconst filePath = this.getPipelineStatePath(featureId);\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t// ─── Skills ──────────────────────────────────────────────────────────────\n\n\t/** Returns the path to .vibe/skills/ directory. */\n\tgetSkillsDir(): string {\n\t\treturn join(this.vibeDir, \"skills\");\n\t}\n\n\t/** Returns a list of directories under .vibe/skills/ that contain SKILL.md. */\n\tasync listSkills(): Promise<string[]> {\n\t\tconst dir = this.getSkillsDir();\n\t\tif (!(await this.fileExists(dir))) return [];\n\t\tconst entries = await readdir(dir, { withFileTypes: true });\n\t\tconst skills: string[] = [];\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tconst skillFile = join(dir, entry.name, \"SKILL.md\");\n\t\t\t\tif (await this.fileExists(skillFile)) {\n\t\t\t\t\tskills.push(entry.name);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn skills.sort();\n\t}\n\n\t/** Checks if a specific vibe skill exists. */\n\tasync hasSkill(skillName: string): Promise<boolean> {\n\t\treturn this.fileExists(join(this.getSkillsDir(), skillName, \"SKILL.md\"));\n\t}\n\n\t/** Reads the full contents of a vibe skill's SKILL.md. */\n\tasync readSkillContent(skillName: string): Promise<string> {\n\t\treturn readFile(join(this.getSkillsDir(), skillName, \"SKILL.md\"), \"utf-8\");\n\t}\n\n\t/** Extracts only the description from a vibe skill's SKILL.md frontmatter. Returns null if not found. */\n\tasync readSkillDescription(skillName: string): Promise<string | null> {\n\t\tconst content = await this.readSkillContent(skillName);\n\t\tconst match = content.match(/^---\\s*\\n([\\s\\S]*?)\\n---/);\n\t\tif (!match) return null;\n\t\tconst frontmatter = match[1];\n\t\tconst descMatch = frontmatter.match(/description:\\s*(.+)/);\n\t\treturn descMatch ? descMatch[1].trim() : null;\n\t}\n\n\t// ─── Reset ───────────────────────────────────────────────────────────────\n\n\t/** Deletes the entire .vibe/ directory and re-initializes from scratch. */\n\tasync reset(): Promise<void> {\n\t\tif (await this.fileExists(this.vibeDir)) {\n\t\t\tawait rm(this.vibeDir, { recursive: true, force: true });\n\t\t}\n\t\tawait this.init();\n\t}\n\n\t// ─── Helpers ─────────────────────────────────────────────────────────────\n\n\t/**\n\t * Ensures the project root .gitignore contains vibe-managed rules.\n\t * Appends a managed block if missing, or replaces it if already present.\n\t */\n\tprivate async ensureRootGitignore(): Promise<void> {\n\t\tconst gitignorePath = join(this.projectRoot, \".gitignore\");\n\t\tlet content = \"\";\n\t\tif (await this.fileExists(gitignorePath)) {\n\t\t\tcontent = await readFile(gitignorePath, \"utf-8\");\n\t\t}\n\n\t\tif (content.includes(VIBE_GITIGNORE_MARKER)) {\n\t\t\t// Replace existing managed block\n\t\t\tconst startIdx = content.indexOf(VIBE_GITIGNORE_MARKER);\n\t\t\tconst endIdx = content.indexOf(VIBE_GITIGNORE_MARKER_END);\n\t\t\tif (endIdx !== -1) {\n\t\t\t\tconst before = content.slice(0, startIdx);\n\t\t\t\tconst after = content.slice(endIdx + VIBE_GITIGNORE_MARKER_END.length);\n\t\t\t\tcontent = before + VIBE_GITIGNORE_BLOCK + after;\n\t\t\t}\n\t\t} else {\n\t\t\t// Append managed block\n\t\t\tconst separator = content.length > 0 && !content.endsWith(\"\\n\") ? \"\\n\\n\" : content.length > 0 ? \"\\n\" : \"\";\n\t\t\tcontent = `${content}${separator}${VIBE_GITIGNORE_BLOCK}\\n`;\n\t\t}\n\n\t\tawait writeFile(gitignorePath, content, \"utf-8\");\n\t}\n\n\tprivate async fileExists(path: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait access(path);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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","sourcesContent":["import { access, mkdir, readdir, readFile, rm, stat, unlink, writeFile } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport { createModuleLogger, type ModuleLogger, noopLogger, type VibeLogger } from \"./logger.js\";\nimport type {\n\tAgentCheckpoint,\n\tArtifactName,\n\tFeatureStatus,\n\tFeatureStatusValue,\n\tOrchestrationState,\n\tPersistedPipeline,\n\tPlanData,\n\tVibeConfig,\n} from \"./types.js\";\n\n/** Valid state transition map. */\nconst VALID_TRANSITIONS: Record<FeatureStatusValue, FeatureStatusValue[]> = {\n\tplanned: [\"designing\", \"blocked\", \"failed\"],\n\tdesigning: [\"branching\", \"implementing\", \"blocked\", \"failed\"],\n\tbranching: [\"implementing\", \"blocked\", \"failed\"],\n\timplementing: [\"testing\", \"done\", \"blocked\", \"failed\"],\n\ttesting: [\"reviewing\", \"implementing\", \"blocked\", \"failed\"],\n\treviewing: [\"done\", \"merging\", \"implementing\", \"blocked\", \"failed\"],\n\tmerging: [\"done\", \"blocked\", \"failed\"],\n\tdone: [],\n\tblocked: [\"planned\", \"designing\", \"branching\", \"implementing\", \"testing\", \"reviewing\", \"merging\", \"failed\"],\n\tfailed: [],\n};\n\n/** Default configuration. */\nconst DEFAULT_CONFIG: VibeConfig = {\n\tautonomyLevel: \"gate_auto\",\n\tgates: {\n\t\trequireRequirementsApproval: true,\n\t\trequirePlanApproval: true,\n\t\trequireDesignApproval: false,\n\t\trequireSystemDesignApproval: true,\n\t\trequireMergeApproval: true,\n\t\trequireDiagnosisApproval: true,\n\t},\n\tretry: {\n\t\tmaxTestRetries: 3,\n\t\tmaxReviewRetries: 2,\n\t\tmaxRegressionRetries: 3,\n\t\tmaxStepRetries: 2,\n\t},\n\tstandards: {\n\t\tsources: [\".vibe/standards/\"],\n\t},\n\tnotifications: {\n\t\tonStepComplete: true,\n\t\tonBlocked: true,\n\t\tonPRReady: true,\n\t\tchannel: \"terminal\",\n\t},\n\tprojectAnalysis: {\n\t\tautoAnalyzeOnInit: true,\n\t\tstaleThresholdDays: 7,\n\t\texcludePaths: [\"node_modules\", \"dist\", \".git\"],\n\t},\n\tdiscovery: {\n\t\tmaxQuestionRounds: 5,\n\t\tskipIfClear: true,\n\t},\n\tcompaction: {\n\t\tenabled: true,\n\t\treserveTokens: 16384,\n\t\tkeepRecentTokens: 20000,\n\t},\n\ttesting: {\n\t\ttestCommand: \"\",\n\t\ttestTimeout: 300,\n\t\trunExistingTests: false,\n\t\texcludePatterns: [],\n\t\tciChecks: [],\n\t},\n};\n\n/** Marker comment used to identify vibe-managed gitignore block. */\nconst VIBE_GITIGNORE_MARKER = \"# >>> vibe managed\";\nconst VIBE_GITIGNORE_MARKER_END = \"# <<< vibe managed\";\n\n/** Gitignore rules appended to project root .gitignore. */\nconst VIBE_GITIGNORE_BLOCK = `${VIBE_GITIGNORE_MARKER}\n.vibe/logs/\n.vibe/orchestration-state.json\n.vibe/requirements.md\n.vibe/project-context.md\n.vibe/agent-history/\n.vibe/features/*/agent-history/\n${VIBE_GITIGNORE_MARKER_END}`;\n\n/** Deep merges two objects. Keys missing from target are taken from defaults. */\nfunction deepMerge(target: Record<string, unknown>, defaults: Record<string, unknown>): Record<string, unknown> {\n\tconst result = { ...defaults };\n\tfor (const key of Object.keys(target)) {\n\t\tconst targetVal = target[key];\n\t\tconst defaultVal = defaults[key];\n\t\tif (\n\t\t\ttargetVal !== undefined &&\n\t\t\ttargetVal !== null &&\n\t\t\ttypeof targetVal === \"object\" &&\n\t\t\t!Array.isArray(targetVal) &&\n\t\t\ttypeof defaultVal === \"object\" &&\n\t\t\tdefaultVal !== null &&\n\t\t\t!Array.isArray(defaultVal)\n\t\t) {\n\t\t\tresult[key] = deepMerge(targetVal as Record<string, unknown>, defaultVal as Record<string, unknown>);\n\t\t} else if (targetVal !== undefined) {\n\t\t\tresult[key] = targetVal;\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Manages .vibe/ directory creation, feature state machine, project config loading,\n * and standard path resolution.\n */\nexport class VibeStore {\n\tprivate readonly vibeDir: string;\n\tprivate readonly featuresDir: string;\n\tprivate readonly standardsDir: string;\n\tprivate readonly log: ModuleLogger;\n\n\tconstructor(\n\t\tprivate readonly projectRoot: string,\n\t\tlogger?: VibeLogger,\n\t) {\n\t\tthis.vibeDir = join(projectRoot, \".vibe\");\n\t\tthis.featuresDir = join(this.vibeDir, \"features\");\n\t\tthis.standardsDir = join(this.vibeDir, \"standards\");\n\t\tthis.log = createModuleLogger(logger ?? noopLogger, \"store\");\n\t}\n\n\t/** Returns the project root path. */\n\tgetProjectRoot(): string {\n\t\treturn this.projectRoot;\n\t}\n\n\t// ─── Directory ───────────────────────────────────────────────────────────\n\n\t/** Initializes the .vibe/ directory structure. */\n\tasync init(): Promise<void> {\n\t\tawait mkdir(this.featuresDir, { recursive: true });\n\t\tawait mkdir(this.standardsDir, { recursive: true });\n\t\tconst configPath = join(this.vibeDir, \"config.json\");\n\t\tif (!(await this.fileExists(configPath))) {\n\t\t\tawait writeFile(configPath, `${JSON.stringify(DEFAULT_CONFIG, null, \"\\t\")}\\n`, \"utf-8\");\n\t\t}\n\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tif (!(await this.fileExists(planPath))) {\n\t\t\tconst emptyPlan: PlanData = {\n\t\t\t\tproject: \"\",\n\t\t\t\tdescription: \"\",\n\t\t\t\tworkflowType: \"new_feature\",\n\t\t\t\tcreatedAt: new Date().toISOString(),\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t\tfeatures: [],\n\t\t\t\tdependencyGraph: {},\n\t\t\t};\n\t\t\tawait writeFile(planPath, `${JSON.stringify(emptyPlan, null, \"\\t\")}\\n`, \"utf-8\");\n\t\t}\n\n\t\tawait this.ensureRootGitignore();\n\t}\n\n\t/** Check if .vibe/ directory has been initialized. */\n\tasync isInitialized(): Promise<boolean> {\n\t\treturn this.fileExists(this.vibeDir);\n\t}\n\n\tgetVibeDir(): string {\n\t\treturn this.vibeDir;\n\t}\n\n\tgetFeaturesDir(): string {\n\t\treturn this.featuresDir;\n\t}\n\n\tgetStandardsDir(): string {\n\t\treturn this.standardsDir;\n\t}\n\n\tgetFeatureDir(featureId: string): string {\n\t\treturn join(this.featuresDir, featureId);\n\t}\n\n\t/** Lists all feature IDs (directory names under features/). */\n\tasync listFeatures(): Promise<string[]> {\n\t\tif (!(await this.fileExists(this.featuresDir))) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(this.featuresDir, { withFileTypes: true });\n\t\treturn entries\n\t\t\t.filter((e) => e.isDirectory())\n\t\t\t.map((e) => e.name)\n\t\t\t.sort();\n\t}\n\n\t/** Creates the feature directory if it doesn't exist and returns the path. */\n\tasync ensureFeatureDir(featureId: string): Promise<string> {\n\t\tconst dir = this.getFeatureDir(featureId);\n\t\tawait mkdir(dir, { recursive: true });\n\t\treturn dir;\n\t}\n\n\t// ─── Config ──────────────────────────────────────────────────────────────\n\n\t/** Reads config.json and returns a complete VibeConfig merged with defaults. */\n\tasync loadConfig(): Promise<VibeConfig> {\n\t\tconst configPath = join(this.vibeDir, \"config.json\");\n\t\tif (!(await this.fileExists(configPath))) {\n\t\t\treturn { ...DEFAULT_CONFIG };\n\t\t}\n\t\tconst raw = await readFile(configPath, \"utf-8\");\n\t\tconst partial = JSON.parse(raw) as Record<string, unknown>;\n\t\treturn deepMerge(partial, DEFAULT_CONFIG as unknown as Record<string, unknown>) as unknown as VibeConfig;\n\t}\n\n\t/** Saves configuration to config.json. */\n\tasync saveConfig(config: VibeConfig): Promise<void> {\n\t\tconst configPath = join(this.vibeDir, \"config.json\");\n\t\tawait writeFile(configPath, `${JSON.stringify(config, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\t// ─── Plan ────────────────────────────────────────────────────────────────\n\n\t/** Reads plan.json. */\n\t/** Checks if a meaningful plan exists. Returns true if plan.json exists and has at least one feature. */\n\tasync hasPlan(): Promise<boolean> {\n\t\ttry {\n\t\t\tconst raw = await readFile(join(this.vibeDir, \"plan.json\"), \"utf-8\");\n\t\t\tconst plan = JSON.parse(raw) as PlanData;\n\t\t\treturn plan.features.length > 0;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tasync loadPlan(): Promise<PlanData> {\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tconst raw = await readFile(planPath, \"utf-8\");\n\t\treturn JSON.parse(raw) as PlanData;\n\t}\n\n\t/** Writes plan.json. */\n\tasync savePlan(plan: PlanData): Promise<void> {\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tawait writeFile(planPath, `${JSON.stringify(plan, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\t// ─── Feature Status ──────────────────────────────────────────────────────\n\n\t/** Reads the feature's status.json. */\n\tasync loadFeatureStatus(featureId: string): Promise<FeatureStatus> {\n\t\tconst statusPath = join(this.getFeatureDir(featureId), \"status.json\");\n\t\tconst raw = await readFile(statusPath, \"utf-8\");\n\t\treturn JSON.parse(raw) as FeatureStatus;\n\t}\n\n\t/**\n\t * Updates feature status. Validates state transitions and syncs status.json with plan.json.\n\t * Auto-creates the feature directory and status.json if they don't exist.\n\t */\n\tasync updateFeatureStatus(featureId: string, newStatus: FeatureStatusValue): Promise<void> {\n\t\tthis.log.debug(`Updating feature status: ${featureId} → ${newStatus}`, { featureId, newStatus });\n\t\tawait this.ensureFeatureDir(featureId);\n\t\tconst statusPath = join(this.getFeatureDir(featureId), \"status.json\");\n\n\t\tlet currentStatus: FeatureStatus;\n\t\tif (await this.fileExists(statusPath)) {\n\t\t\tconst raw = await readFile(statusPath, \"utf-8\");\n\t\t\tcurrentStatus = JSON.parse(raw) as FeatureStatus;\n\n\t\t\t// Idempotent: same-state transition is a no-op\n\t\t\tif (currentStatus.status === newStatus) {\n\t\t\t\tthis.log.debug(`Status already \"${newStatus}\", skipping transition`, { featureId });\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst allowed = VALID_TRANSITIONS[currentStatus.status];\n\t\t\tif (!allowed.includes(newStatus)) {\n\t\t\t\tthis.log.warn(`Invalid state transition: \"${currentStatus.status}\" → \"${newStatus}\"`, { featureId });\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Invalid state transition: \"${currentStatus.status}\" → \"${newStatus}\" for feature \"${featureId}\". Allowed transitions: [${allowed.join(\", \")}]`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else {\n\t\t\tcurrentStatus = {\n\t\t\t\tstatus: \"planned\",\n\t\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\t};\n\t\t\t// New features can only start from planned. Validate planned → newStatus transition.\n\t\t\tif (newStatus !== \"planned\") {\n\t\t\t\tconst allowed = VALID_TRANSITIONS.planned;\n\t\t\t\tif (!allowed.includes(newStatus)) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`Invalid state transition: \"planned\" → \"${newStatus}\" for new feature \"${featureId}\". Allowed transitions: [${allowed.join(\", \")}]`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst updated: FeatureStatus = {\n\t\t\tstatus: newStatus,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t};\n\t\tawait writeFile(statusPath, `${JSON.stringify(updated, null, \"\\t\")}\\n`, \"utf-8\");\n\n\t\t// Sync with plan.json\n\t\tawait this.syncPlanFeatureStatus(featureId, newStatus);\n\t}\n\n\t/** Syncs the feature's status within plan.json. */\n\tprivate async syncPlanFeatureStatus(featureId: string, status: FeatureStatusValue): Promise<void> {\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tif (!(await this.fileExists(planPath))) {\n\t\t\treturn;\n\t\t}\n\t\tconst plan = await this.loadPlan();\n\t\tconst feature = plan.features.find((f) => f.featureId === featureId);\n\t\tif (feature) {\n\t\t\tfeature.status = status;\n\t\t\tplan.updatedAt = new Date().toISOString();\n\t\t\tawait this.savePlan(plan);\n\t\t}\n\t}\n\n\t// ─── Artifacts ───────────────────────────────────────────────────────────\n\n\t/** Returns the absolute path for an artifact file. */\n\tgetArtifactPath(featureId: string, artifact: ArtifactName): string {\n\t\treturn join(this.getFeatureDir(featureId), artifact);\n\t}\n\n\t/** Checks if an artifact file exists. */\n\tasync hasArtifact(featureId: string, artifact: ArtifactName): Promise<boolean> {\n\t\treturn this.fileExists(this.getArtifactPath(featureId, artifact));\n\t}\n\n\t/** Reads an artifact file. */\n\tasync readArtifact(featureId: string, artifact: ArtifactName): Promise<string> {\n\t\treturn readFile(this.getArtifactPath(featureId, artifact), \"utf-8\");\n\t}\n\n\t/** Writes an artifact file. Auto-creates the feature directory if missing. */\n\tasync writeArtifact(featureId: string, artifact: ArtifactName, content: string): Promise<void> {\n\t\tthis.log.debug(`Writing artifact: ${artifact}`, { featureId });\n\t\tawait this.ensureFeatureDir(featureId);\n\t\tawait writeFile(this.getArtifactPath(featureId, artifact), content, \"utf-8\");\n\t}\n\n\t/** Writes a global artifact file to the .vibe/ root. */\n\tasync writeGlobalArtifact(filename: string, content: string): Promise<void> {\n\t\tawait writeFile(join(this.vibeDir, filename), content, \"utf-8\");\n\t}\n\n\t/** Reads a global artifact file from the .vibe/ root. */\n\tasync readGlobalArtifact(filename: string): Promise<string> {\n\t\treturn readFile(join(this.vibeDir, filename), \"utf-8\");\n\t}\n\n\t/** Checks if a global artifact file exists. */\n\tasync hasGlobalArtifact(filename: string): Promise<boolean> {\n\t\treturn this.fileExists(join(this.vibeDir, filename));\n\t}\n\n\t// ─── Standards ───────────────────────────────────────────────────────────\n\n\t/** Returns a list of .md files in .vibe/standards/ (filenames only). */\n\tasync listStandardFiles(): Promise<string[]> {\n\t\tif (!(await this.fileExists(this.standardsDir))) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(this.standardsDir);\n\t\treturn entries.filter((e) => e.endsWith(\".md\")).sort();\n\t}\n\n\t/** Resolves a standards source path to an absolute path. */\n\tresolveStandardSource(source: string): string {\n\t\tif (isAbsolute(source)) {\n\t\t\treturn source;\n\t\t}\n\t\treturn resolve(this.projectRoot, source);\n\t}\n\n\t/** Checks if a standard file exists in .vibe/standards/. */\n\tasync hasStandardFile(fileName: string): Promise<boolean> {\n\t\treturn this.fileExists(join(this.standardsDir, fileName));\n\t}\n\n\t/** Reads all standard files and returns them as Map<filename, content>. */\n\tasync readAllStandards(): Promise<Map<string, string>> {\n\t\tconst files = await this.listStandardFiles();\n\t\tconst result = new Map<string, string>();\n\t\tfor (const fileName of files) {\n\t\t\tconst content = await readFile(join(this.standardsDir, fileName), \"utf-8\");\n\t\t\tresult.set(fileName, content);\n\t\t}\n\t\treturn result;\n\t}\n\n\t/** Writes a single standard file. */\n\tasync writeStandard(fileName: string, content: string): Promise<void> {\n\t\tawait writeFile(join(this.standardsDir, fileName), content, \"utf-8\");\n\t}\n\n\t// ─── Requirements ────────────────────────────────────────────────────────\n\n\t/** Returns the path to .vibe/requirements.md. */\n\tgetRequirementsPath(): string {\n\t\treturn join(this.vibeDir, \"requirements.md\");\n\t}\n\n\t/** Checks if requirements.md exists. */\n\tasync hasRequirements(): Promise<boolean> {\n\t\treturn this.fileExists(this.getRequirementsPath());\n\t}\n\n\t/** Reads requirements.md. */\n\tasync readRequirements(): Promise<string> {\n\t\treturn readFile(this.getRequirementsPath(), \"utf-8\");\n\t}\n\n\t/** Writes requirements.md. */\n\tasync writeRequirements(content: string): Promise<void> {\n\t\tawait writeFile(this.getRequirementsPath(), content, \"utf-8\");\n\t}\n\n\t/** Deletes requirements.md. */\n\tasync clearRequirements(): Promise<void> {\n\t\tconst filePath = this.getRequirementsPath();\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t// ─── System Design ───────────────────────────────────────────────────────\n\n\t/** Returns the path to .vibe/system-design.md. */\n\tgetSystemDesignPath(): string {\n\t\treturn join(this.vibeDir, \"system-design.md\");\n\t}\n\n\t/** Checks if system-design.md exists. */\n\tasync hasSystemDesign(): Promise<boolean> {\n\t\treturn this.fileExists(this.getSystemDesignPath());\n\t}\n\n\t/** Reads system-design.md. */\n\tasync readSystemDesign(): Promise<string> {\n\t\treturn readFile(this.getSystemDesignPath(), \"utf-8\");\n\t}\n\n\t/** Writes system-design.md. */\n\tasync writeSystemDesign(content: string): Promise<void> {\n\t\tawait writeFile(this.getSystemDesignPath(), content, \"utf-8\");\n\t}\n\n\t/** Deletes system-design.md. */\n\tasync clearSystemDesign(): Promise<void> {\n\t\tconst filePath = this.getSystemDesignPath();\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t/** Deletes plan.json. */\n\tasync clearPlan(): Promise<void> {\n\t\tconst planPath = join(this.vibeDir, \"plan.json\");\n\t\tif (await this.fileExists(planPath)) {\n\t\t\tawait unlink(planPath);\n\t\t}\n\t}\n\n\t/** Recursively deletes the entire feature directory. */\n\tasync clearFeatureDir(featureId: string): Promise<void> {\n\t\tconst dir = this.getFeatureDir(featureId);\n\t\tif (await this.fileExists(dir)) {\n\t\t\tawait rm(dir, { recursive: true, force: true });\n\t\t}\n\t}\n\n\t/** Deletes all feature directories under features/. */\n\tasync clearAllFeatures(): Promise<void> {\n\t\tconst dir = this.getFeaturesDir();\n\t\tif (!(await this.fileExists(dir))) return;\n\t\tconst entries = await readdir(dir);\n\t\tfor (const entry of entries) {\n\t\t\tawait rm(join(dir, entry), { recursive: true, force: true });\n\t\t}\n\t}\n\n\t/** Deletes global agent history by role name. (e.g., \"discovery\" → discovery-*.json) */\n\tasync clearGlobalAgentHistoryByRole(role: string): Promise<void> {\n\t\tconst dir = this.getGlobalAgentHistoryDir();\n\t\tif (!(await this.fileExists(dir))) return;\n\t\tconst entries = await readdir(dir);\n\t\tfor (const entry of entries) {\n\t\t\tif (entry.startsWith(`${role}-`) && entry.endsWith(\".json\")) {\n\t\t\t\tawait unlink(join(dir, entry));\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Deletes a global artifact. */\n\tasync clearGlobalArtifact(filename: string): Promise<void> {\n\t\tconst filePath = join(this.vibeDir, filename);\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t// ─── Project Context ─────────────────────────────────────────────────────\n\n\t/** Returns the path to .vibe/project-context.md. */\n\tgetProjectContextPath(): string {\n\t\treturn join(this.vibeDir, \"project-context.md\");\n\t}\n\n\t/** Checks if project-context.md exists. */\n\tasync hasProjectContext(): Promise<boolean> {\n\t\treturn this.fileExists(this.getProjectContextPath());\n\t}\n\n\t/** Reads project-context.md. */\n\tasync readProjectContext(): Promise<string> {\n\t\treturn readFile(this.getProjectContextPath(), \"utf-8\");\n\t}\n\n\t/** Writes project-context.md. */\n\tasync writeProjectContext(content: string): Promise<void> {\n\t\tawait writeFile(this.getProjectContextPath(), content, \"utf-8\");\n\t}\n\n\t/** Checks if project-context.md is older than staleThresholdDays. */\n\tasync isProjectContextStale(thresholdDays: number): Promise<boolean> {\n\t\tconst contextPath = this.getProjectContextPath();\n\t\tif (!(await this.fileExists(contextPath))) {\n\t\t\treturn true;\n\t\t}\n\t\tconst fileStat = await stat(contextPath);\n\t\tconst ageMs = Date.now() - fileStat.mtimeMs;\n\t\tconst ageDays = ageMs / (1000 * 60 * 60 * 24);\n\t\treturn ageDays > thresholdDays;\n\t}\n\n\t// ─── Agent History (Feature Level) ───────────────────────────────────────\n\n\t/** Returns the agent history directory path for a feature. */\n\tgetAgentHistoryDir(featureId: string): string {\n\t\treturn join(this.getFeatureDir(featureId), \"agent-history\");\n\t}\n\n\t/** Returns the agent history file path. */\n\tgetAgentHistoryPath(featureId: string, role: string, action: string): string {\n\t\treturn join(this.getAgentHistoryDir(featureId), `${role}-${action}.json`);\n\t}\n\n\t/** Saves agent history. Auto-creates the directory if missing. */\n\tasync saveAgentHistory(featureId: string, checkpoint: AgentCheckpoint): Promise<void> {\n\t\tthis.log.debug(`Saving agent history: ${checkpoint.role}:${checkpoint.action}`, { featureId });\n\t\tconst dir = this.getAgentHistoryDir(featureId);\n\t\tawait mkdir(dir, { recursive: true });\n\t\tconst filePath = this.getAgentHistoryPath(featureId, checkpoint.role, checkpoint.action);\n\t\tawait writeFile(filePath, JSON.stringify(checkpoint, null, \"\\t\"), \"utf-8\");\n\t}\n\n\t/** Loads agent history. Returns null if not found. */\n\tasync loadAgentHistory(featureId: string, role: string, action: string): Promise<AgentCheckpoint | null> {\n\t\tconst filePath = this.getAgentHistoryPath(featureId, role, action);\n\t\tif (!(await this.fileExists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\treturn JSON.parse(raw) as AgentCheckpoint;\n\t}\n\n\t/** Checks if agent history exists. */\n\tasync hasAgentHistory(featureId: string, role: string, action: string): Promise<boolean> {\n\t\treturn this.fileExists(this.getAgentHistoryPath(featureId, role, action));\n\t}\n\n\t/**\n\t * Loads prior action histories for the same role in chronological order.\n\t * Excludes the currentAction's own history (separated from retry checkpointing).\n\t */\n\tasync loadPriorAgentHistory(featureId: string, role: string, currentAction: string): Promise<AgentCheckpoint[]> {\n\t\tconst dir = this.getAgentHistoryDir(featureId);\n\t\tif (!(await this.fileExists(dir))) return [];\n\n\t\tconst entries = await readdir(dir);\n\t\tconst prefix = `${role}-`;\n\t\tconst checkpoints: AgentCheckpoint[] = [];\n\n\t\tfor (const entry of entries) {\n\t\t\tif (!entry.startsWith(prefix) || !entry.endsWith(\".json\")) continue;\n\t\t\tconst action = entry.slice(prefix.length, -5);\n\t\t\tif (action === currentAction) continue;\n\n\t\t\tconst raw = await readFile(join(dir, entry), \"utf-8\");\n\t\t\tcheckpoints.push(JSON.parse(raw) as AgentCheckpoint);\n\t\t}\n\n\t\treturn checkpoints.sort((a, b) => a.timestamp.localeCompare(b.timestamp));\n\t}\n\n\t/** Returns all agent history filenames for a feature. */\n\tasync listAgentHistory(featureId: string): Promise<string[]> {\n\t\tconst dir = this.getAgentHistoryDir(featureId);\n\t\tif (!(await this.fileExists(dir))) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(dir);\n\t\treturn entries.filter((e) => e.endsWith(\".json\")).sort();\n\t}\n\n\t// ─── Agent History (Global Level) ────────────────────────────────────────\n\n\t/** Returns the project-level agent history directory path. */\n\tgetGlobalAgentHistoryDir(): string {\n\t\treturn join(this.vibeDir, \"agent-history\");\n\t}\n\n\t/** Returns the project-level agent history file path. */\n\tgetGlobalAgentHistoryPath(role: string, action: string): string {\n\t\treturn join(this.getGlobalAgentHistoryDir(), `${role}-${action}.json`);\n\t}\n\n\t/** Saves project-level agent history. */\n\tasync saveGlobalAgentHistory(checkpoint: AgentCheckpoint): Promise<void> {\n\t\tconst dir = this.getGlobalAgentHistoryDir();\n\t\tawait mkdir(dir, { recursive: true });\n\t\tconst filePath = this.getGlobalAgentHistoryPath(checkpoint.role, checkpoint.action);\n\t\tawait writeFile(filePath, JSON.stringify(checkpoint, null, \"\\t\"), \"utf-8\");\n\t}\n\n\t/** Loads project-level agent history. Returns null if not found. */\n\tasync loadGlobalAgentHistory(role: string, action: string): Promise<AgentCheckpoint | null> {\n\t\tconst filePath = this.getGlobalAgentHistoryPath(role, action);\n\t\tif (!(await this.fileExists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\treturn JSON.parse(raw) as AgentCheckpoint;\n\t}\n\n\t/** Checks if project-level agent history exists. */\n\tasync hasGlobalAgentHistory(role: string, action: string): Promise<boolean> {\n\t\treturn this.fileExists(this.getGlobalAgentHistoryPath(role, action));\n\t}\n\n\t/** Returns a list of project-level agent history files. */\n\tasync listGlobalAgentHistory(): Promise<string[]> {\n\t\tconst dir = this.getGlobalAgentHistoryDir();\n\t\tif (!(await this.fileExists(dir))) {\n\t\t\treturn [];\n\t\t}\n\t\tconst entries = await readdir(dir);\n\t\treturn entries.filter((e) => e.endsWith(\".json\")).sort();\n\t}\n\n\t// ─── Orchestration State Persistence ─────────────────────────────────────\n\n\t/** Returns the path to orchestration-state.json. */\n\tgetOrchestrationStatePath(): string {\n\t\treturn join(this.vibeDir, \"orchestration-state.json\");\n\t}\n\n\t/** Saves orchestration state. Auto-creates .vibe/ directory if missing. */\n\tasync saveOrchestrationState(state: OrchestrationState): Promise<void> {\n\t\tawait mkdir(this.vibeDir, { recursive: true });\n\t\tawait writeFile(this.getOrchestrationStatePath(), `${JSON.stringify(state, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\t/** Loads orchestration state. Returns null if not found. */\n\tasync loadOrchestrationState(): Promise<OrchestrationState | null> {\n\t\tconst filePath = this.getOrchestrationStatePath();\n\t\tif (!(await this.fileExists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\treturn JSON.parse(raw) as OrchestrationState;\n\t}\n\n\t/** Deletes the orchestration state file. */\n\tasync clearOrchestrationState(): Promise<void> {\n\t\tconst filePath = this.getOrchestrationStatePath();\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t/** Checks if orchestration state file exists. */\n\tasync hasOrchestrationState(): Promise<boolean> {\n\t\treturn this.fileExists(this.getOrchestrationStatePath());\n\t}\n\n\t// ─── Pipeline State Persistence ──────────────────────────────────────────\n\n\t/** Returns the path to pipeline.json. */\n\tgetPipelineStatePath(featureId: string): string {\n\t\treturn join(this.getFeatureDir(featureId), \"pipeline.json\");\n\t}\n\n\t/** Saves pipeline state. */\n\tasync savePipelineState(featureId: string, state: PersistedPipeline): Promise<void> {\n\t\tawait this.ensureFeatureDir(featureId);\n\t\tawait writeFile(this.getPipelineStatePath(featureId), `${JSON.stringify(state, null, \"\\t\")}\\n`, \"utf-8\");\n\t}\n\n\t/** Loads pipeline state. Returns null if not found. */\n\tasync loadPipelineState(featureId: string): Promise<PersistedPipeline | null> {\n\t\tconst filePath = this.getPipelineStatePath(featureId);\n\t\tif (!(await this.fileExists(filePath))) {\n\t\t\treturn null;\n\t\t}\n\t\tconst raw = await readFile(filePath, \"utf-8\");\n\t\treturn JSON.parse(raw) as PersistedPipeline;\n\t}\n\n\t/** Deletes the pipeline state file. */\n\tasync clearPipelineState(featureId: string): Promise<void> {\n\t\tconst filePath = this.getPipelineStatePath(featureId);\n\t\tif (await this.fileExists(filePath)) {\n\t\t\tawait unlink(filePath);\n\t\t}\n\t}\n\n\t// ─── Reset ───────────────────────────────────────────────────────────────\n\n\t/** Deletes the entire .vibe/ directory and re-initializes from scratch. */\n\tasync reset(): Promise<void> {\n\t\tif (await this.fileExists(this.vibeDir)) {\n\t\t\tawait rm(this.vibeDir, { recursive: true, force: true });\n\t\t}\n\t\tawait this.init();\n\t}\n\n\t// ─── Helpers ─────────────────────────────────────────────────────────────\n\n\t/**\n\t * Ensures the project root .gitignore contains vibe-managed rules.\n\t * Appends a managed block if missing, or replaces it if already present.\n\t */\n\tprivate async ensureRootGitignore(): Promise<void> {\n\t\tconst gitignorePath = join(this.projectRoot, \".gitignore\");\n\t\tlet content = \"\";\n\t\tif (await this.fileExists(gitignorePath)) {\n\t\t\tcontent = await readFile(gitignorePath, \"utf-8\");\n\t\t}\n\n\t\tif (content.includes(VIBE_GITIGNORE_MARKER)) {\n\t\t\t// Replace existing managed block\n\t\t\tconst startIdx = content.indexOf(VIBE_GITIGNORE_MARKER);\n\t\t\tconst endIdx = content.indexOf(VIBE_GITIGNORE_MARKER_END);\n\t\t\tif (endIdx !== -1) {\n\t\t\t\tconst before = content.slice(0, startIdx);\n\t\t\t\tconst after = content.slice(endIdx + VIBE_GITIGNORE_MARKER_END.length);\n\t\t\t\tcontent = before + VIBE_GITIGNORE_BLOCK + after;\n\t\t\t}\n\t\t} else {\n\t\t\t// Append managed block\n\t\t\tconst separator = content.length > 0 && !content.endsWith(\"\\n\") ? \"\\n\\n\" : content.length > 0 ? \"\\n\" : \"\";\n\t\t\tcontent = `${content}${separator}${VIBE_GITIGNORE_BLOCK}\\n`;\n\t\t}\n\n\t\tawait writeFile(gitignorePath, content, \"utf-8\");\n\t}\n\n\tprivate async fileExists(path: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait access(path);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"]}