create-quiver 0.12.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +49 -17
  3. package/README_FOR_AI.md +31 -29
  4. package/ROADMAP.md +15 -3
  5. package/docs/AI_ONBOARDING_PROMPT.md.template +7 -1
  6. package/docs/COMMANDS.md.template +44 -18
  7. package/docs/STATUS.md.template +5 -1
  8. package/docs/WORKFLOW.md.template +13 -11
  9. package/package.json +9 -3
  10. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/EVIDENCE_REPORT.md +293 -0
  11. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/EXECUTION_PLAN.md +58 -0
  12. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/SPEC.md +242 -0
  13. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/STATUS.md +35 -0
  14. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/pr.md +77 -0
  15. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +34 -0
  16. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +52 -0
  17. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/slice.json +52 -0
  18. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/CLOSURE_BRIEF.md +36 -0
  19. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/EXECUTION_BRIEF.md +52 -0
  20. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/slice.json +56 -0
  21. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/CLOSURE_BRIEF.md +43 -0
  22. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/EXECUTION_BRIEF.md +54 -0
  23. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/slice.json +52 -0
  24. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/CLOSURE_BRIEF.md +35 -0
  25. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/EXECUTION_BRIEF.md +53 -0
  26. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/slice.json +54 -0
  27. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/CLOSURE_BRIEF.md +34 -0
  28. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/EXECUTION_BRIEF.md +54 -0
  29. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/slice.json +52 -0
  30. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/CLOSURE_BRIEF.md +34 -0
  31. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/EXECUTION_BRIEF.md +54 -0
  32. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/slice.json +53 -0
  33. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/CLOSURE_BRIEF.md +33 -0
  34. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/EXECUTION_BRIEF.md +56 -0
  35. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/slice.json +55 -0
  36. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/CLOSURE_BRIEF.md +33 -0
  37. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/EXECUTION_BRIEF.md +54 -0
  38. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/slice.json +52 -0
  39. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/CLOSURE_BRIEF.md +39 -0
  40. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/EXECUTION_BRIEF.md +56 -0
  41. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/slice.json +53 -0
  42. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/CLOSURE_BRIEF.md +38 -0
  43. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/EXECUTION_BRIEF.md +57 -0
  44. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/slice.json +52 -0
  45. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/CLOSURE_BRIEF.md +39 -0
  46. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/EXECUTION_BRIEF.md +55 -0
  47. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/slice.json +56 -0
  48. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/CLOSURE_BRIEF.md +36 -0
  49. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/EXECUTION_BRIEF.md +54 -0
  50. package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/slice.json +53 -0
  51. package/specs/quiver-v26-0121-smoke-hardening/EVIDENCE_REPORT.md +208 -0
  52. package/specs/quiver-v26-0121-smoke-hardening/EXECUTION_PLAN.md +57 -0
  53. package/specs/quiver-v26-0121-smoke-hardening/SPEC.md +137 -0
  54. package/specs/quiver-v26-0121-smoke-hardening/STATUS.md +32 -0
  55. package/specs/quiver-v26-0121-smoke-hardening/pr.md +96 -0
  56. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/CLOSURE_BRIEF.md +35 -0
  57. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/EXECUTION_BRIEF.md +55 -0
  58. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/slice.json +73 -0
  59. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/CLOSURE_BRIEF.md +38 -0
  60. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/EXECUTION_BRIEF.md +51 -0
  61. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/slice.json +76 -0
  62. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/CLOSURE_BRIEF.md +37 -0
  63. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/EXECUTION_BRIEF.md +52 -0
  64. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/slice.json +75 -0
  65. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/CLOSURE_BRIEF.md +37 -0
  66. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/EXECUTION_BRIEF.md +53 -0
  67. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/slice.json +77 -0
  68. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/CLOSURE_BRIEF.md +35 -0
  69. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/EXECUTION_BRIEF.md +52 -0
  70. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/slice.json +77 -0
  71. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/CLOSURE_BRIEF.md +34 -0
  72. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/EXECUTION_BRIEF.md +54 -0
  73. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/slice.json +84 -0
  74. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/CLOSURE_BRIEF.md +35 -0
  75. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/EXECUTION_BRIEF.md +53 -0
  76. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/slice.json +82 -0
  77. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/CLOSURE_BRIEF.md +35 -0
  78. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/EXECUTION_BRIEF.md +55 -0
  79. package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/slice.json +92 -0
  80. package/src/create-quiver/commands/ai.js +577 -27
  81. package/src/create-quiver/commands/flow.js +6 -5
  82. package/src/create-quiver/commands/graph.js +6 -4
  83. package/src/create-quiver/commands/plan.js +3 -3
  84. package/src/create-quiver/index.js +328 -12
  85. package/src/create-quiver/lib/actionable-error.js +27 -0
  86. package/src/create-quiver/lib/agent-profiles.js +1 -1
  87. package/src/create-quiver/lib/ai/context-packs.js +4 -0
  88. package/src/create-quiver/lib/ai/execution-plan.js +7 -1
  89. package/src/create-quiver/lib/ai/executor.js +270 -20
  90. package/src/create-quiver/lib/ai/export-state.js +534 -0
  91. package/src/create-quiver/lib/ai/github.js +83 -0
  92. package/src/create-quiver/lib/ai/onboarding-template.js +215 -2
  93. package/src/create-quiver/lib/ai/plan-review.js +5 -2
  94. package/src/create-quiver/lib/ai/providers.js +4 -3
  95. package/src/create-quiver/lib/ai/run-state.js +414 -0
  96. package/src/create-quiver/lib/ai/spec-generator.js +12 -0
  97. package/src/create-quiver/lib/ai/spec-templates.js +78 -9
  98. package/src/create-quiver/lib/approvals.js +22 -3
  99. package/src/create-quiver/lib/demo.js +189 -14
  100. package/src/create-quiver/lib/doctor.js +75 -0
  101. package/src/create-quiver/lib/handoff.js +81 -12
  102. package/src/create-quiver/lib/init-docs.js +24 -6
  103. package/src/create-quiver/lib/init-layout.js +8 -0
  104. package/src/create-quiver/lib/json.js +53 -3
  105. package/src/create-quiver/lib/readiness.js +18 -3
  106. package/src/create-quiver/lib/scope.js +50 -7
  107. package/src/create-quiver/lib/slice-graph.js +138 -38
  108. package/src/create-quiver/lib/slice.js +6 -1
  109. package/src/create-quiver/lib/spec-worktrees.js +16 -2
@@ -0,0 +1,27 @@
1
+ function formatActionableError({ failure, impact, fix, nextCommand } = {}) {
2
+ const lines = [`create-quiver: ${String(failure || 'operation failed').trim()}`];
3
+
4
+ if (impact) {
5
+ lines.push(`Impact: ${String(impact).trim()}`);
6
+ }
7
+ if (fix) {
8
+ lines.push(`Fix: ${String(fix).trim()}`);
9
+ }
10
+ if (nextCommand) {
11
+ lines.push(`Next command: ${String(nextCommand).trim()}`);
12
+ }
13
+
14
+ return lines.join('\n');
15
+ }
16
+
17
+ function createActionableError(code, fields = {}, details = {}) {
18
+ const error = new Error(formatActionableError(fields));
19
+ error.code = code;
20
+ error.details = details;
21
+ return error;
22
+ }
23
+
24
+ module.exports = {
25
+ createActionableError,
26
+ formatActionableError,
27
+ };
@@ -4,7 +4,7 @@ const path = require('node:path');
4
4
  const { assertSupportedProvider, formatProviderList } = require('./ai/providers');
5
5
  const { quiverInternalPaths } = require('./init-layout');
6
6
 
7
- const AGENT_PROFILE_ROLES = Object.freeze(['planner', 'executor', 'reviewer', 'researcher']);
7
+ const AGENT_PROFILE_ROLES = Object.freeze(['planner', 'executor', 'reviewer', 'doctor']);
8
8
  const PROFILE_STATE_VERSION = 1;
9
9
 
10
10
  function formatError(message) {
@@ -45,9 +45,13 @@ const DEFAULT_CONTEXT_PACK_BY_ROLE = Object.freeze({
45
45
 
46
46
  const PACK_ORDER = ['full', 'planning', 'slice', 'minimal'];
47
47
  const CONTEXT_PREPARED_DOC_PATHS = Object.freeze([
48
+ 'docs/INDEX.md',
49
+ 'docs/PROJECT_MAP.md',
48
50
  'docs/AI_CONTEXT.md',
49
51
  'docs/AI_ONBOARDING_PROMPT.md',
50
52
  'docs/CONTEXTO.md',
53
+ 'docs/WORKFLOW.md',
54
+ 'docs/ARCHITECTURE.md',
51
55
  'docs/STATUS.md',
52
56
  'docs/DECISIONS.md',
53
57
  ]);
@@ -35,6 +35,9 @@ function summarizeSlice(node, repoRoot) {
35
35
  title: node.title || node.sliceId,
36
36
  status: node.status || 'draft',
37
37
  files: Array.isArray(node.files) ? node.files : [],
38
+ expected_read_paths: Array.isArray(node.expected_read_paths) ? node.expected_read_paths : [],
39
+ allowed_write_paths: Array.isArray(node.allowed_write_paths) ? node.allowed_write_paths : [],
40
+ validation_hints: Array.isArray(node.validation_hints) ? node.validation_hints : [],
38
41
  depends_on: Array.isArray(node.depends_on) ? node.depends_on : [],
39
42
  parallel_safe: node.parallel_safe || null,
40
43
  parallel_safe_reason: node.parallel_safe_reason || null,
@@ -274,7 +277,7 @@ function formatHumanExecutionPlan(report) {
274
277
 
275
278
  for (const level of report.ready_levels) {
276
279
  const modeLabel = level.parallel_ready ? 'parallel-ready' : 'sequential';
277
- lines.push(`Level ${level.index} (${modeLabel})`);
280
+ lines.push(`Wave ${level.index} (${modeLabel})`);
278
281
  lines.push(`Worktree strategy: ${level.worktree_strategy.mode}`);
279
282
  if (level.fallback_reason) {
280
283
  lines.push(`Fallback: ${level.fallback_reason}`);
@@ -282,6 +285,7 @@ function formatHumanExecutionPlan(report) {
282
285
 
283
286
  for (const slice of level.slices) {
284
287
  lines.push(`- ${slice.ref} [${slice.status}]`);
288
+ lines.push(` parallel_safe: ${slice.parallel_safe || 'unspecified'}${slice.parallel_safe_reason ? ` (${slice.parallel_safe_reason})` : ''}`);
285
289
  }
286
290
 
287
291
  if (level.conflicts.length > 0) {
@@ -454,6 +458,7 @@ async function runSequentialGroup(repoRoot, level, group, options = {}) {
454
458
  providerExplicit: options.providerExplicit,
455
459
  role: options.role,
456
460
  slice: slice.slice_path,
461
+ skipWorktreeBranchCheck: true,
457
462
  timeout: options.timeout,
458
463
  });
459
464
  results.push({
@@ -498,6 +503,7 @@ async function runParallelGroupInWorktrees(repoRoot, level, group, options = {})
498
503
  providerExplicit: options.providerExplicit,
499
504
  role: options.role,
500
505
  slice: workspace.slice.slice_path,
506
+ skipWorktreeBranchCheck: true,
501
507
  timeout: options.timeout,
502
508
  });
503
509
  const commit = runGit(['rev-parse', 'HEAD'], workspace.worktreePath);
@@ -5,7 +5,8 @@ const cp = require('node:child_process');
5
5
  const { buildContextPackMetadata, normalizeRole } = require('./context-packs');
6
6
  const { buildProviderInvocation, runProvider } = require('./providers');
7
7
  const { resolveProfileProvider } = require('../agent-profiles');
8
- const { runGit } = require('../git');
8
+ const { currentBranch, runGit } = require('../git');
9
+ const { redactSecrets, truncateText } = require('../evidence');
9
10
  const { captureWorktreeSnapshot, validateScopeSnapshot } = require('../scope');
10
11
  const { resolveSliceContext } = require('../slice');
11
12
 
@@ -77,6 +78,14 @@ function formatList(items) {
77
78
  return items.map((item) => `- ${item}`);
78
79
  }
79
80
 
81
+ function uniqueList(items) {
82
+ return Array.from(new Set((Array.isArray(items) ? items : []).map((item) => String(item)).filter(Boolean)));
83
+ }
84
+
85
+ function escapeRegex(value) {
86
+ return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
87
+ }
88
+
80
89
  function extractMarkdownHeading(text) {
81
90
  const match = String(text || '').match(/^#\s+(.+)$/m);
82
91
  return match ? match[1].trim() : '';
@@ -189,6 +198,9 @@ function buildManualExecutorPrompt({ repoRoot, slicePath, role, context, tokenLi
189
198
  `- Source: ${specExcerpt.path}`,
190
199
  ...specExcerpt.lines,
191
200
  '',
201
+ 'Expected read paths:',
202
+ ...formatList(executorContext.expectedReadPaths),
203
+ '',
192
204
  'Allowed files:',
193
205
  ...formatList(executorContext.allowedFiles),
194
206
  '',
@@ -201,6 +213,9 @@ function buildManualExecutorPrompt({ repoRoot, slicePath, role, context, tokenLi
201
213
  'Validation commands:',
202
214
  ...formatList(executorContext.validationCommands),
203
215
  '',
216
+ 'Validation hints:',
217
+ ...formatList(executorContext.validationHints),
218
+ '',
204
219
  'Exact deliverable expected:',
205
220
  '- Implement only this slice.',
206
221
  '- Keep the change inside the allowed files.',
@@ -289,8 +304,10 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
289
304
  const relativeSlicePath = toRelativePath(canonicalRepoRoot, slice.sliceAbs);
290
305
  const relativeBriefPath = toRelativePath(canonicalRepoRoot, briefPath);
291
306
  const allowedFiles = Array.isArray(slice.files) ? slice.files.map((file) => String(file)) : [];
307
+ const expectedReadPaths = Array.isArray(slice.expectedReadPaths) ? slice.expectedReadPaths.map((file) => String(file)) : [];
292
308
  const acceptance = Array.isArray(slice.acceptance) ? slice.acceptance.map((item) => String(item)) : [];
293
309
  const validationCommands = Array.isArray(slice.tests) ? slice.tests.map((item) => String(item)) : [];
310
+ const validationHints = Array.isArray(slice.validationHints) ? slice.validationHints.map((item) => String(item)) : [];
294
311
  const mustItems = Array.isArray(slice.json.must) ? slice.json.must.map((item) => String(item)) : [];
295
312
  const excludedItems = Array.isArray(slice.json.not_included) ? slice.json.not_included.map((item) => String(item)) : [];
296
313
 
@@ -301,6 +318,8 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
301
318
  `Spec: ${slice.specSlug}`,
302
319
  `Slice file: ${relativeSlicePath}`,
303
320
  `Execution brief: ${relativeBriefPath}`,
321
+ 'Expected read paths:',
322
+ ...formatList(expectedReadPaths),
304
323
  'Allowed files:',
305
324
  ...formatList(allowedFiles),
306
325
  'Acceptance criteria:',
@@ -317,6 +336,10 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
317
336
  sections.push('Not included:', ...formatList(excludedItems));
318
337
  }
319
338
 
339
+ if (validationHints.length > 0) {
340
+ sections.push('Validation hints:', ...formatList(validationHints));
341
+ }
342
+
320
343
  sections.push(
321
344
  'Constraints:',
322
345
  '- Do not commit manually. Quiver can create the slice commit after scope and validation pass when the user enables --commit.',
@@ -332,8 +355,10 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
332
355
  briefPath: relativeBriefPath,
333
356
  briefText,
334
357
  context: pack,
358
+ expectedReadPaths,
335
359
  prompt: sections.join('\n\n'),
336
360
  slice,
361
+ validationHints,
337
362
  validationCommands,
338
363
  };
339
364
  }
@@ -407,18 +432,18 @@ function runValidationCommand(command, repoRoot) {
407
432
  stdio: ['ignore', 'pipe', 'pipe'],
408
433
  });
409
434
  return {
410
- command,
435
+ command: redactSecrets(command),
411
436
  ok: true,
412
- stdout,
437
+ stdout: redactSecrets(stdout),
413
438
  stderr: '',
414
439
  exitCode: 0,
415
440
  };
416
441
  } catch (error) {
417
442
  return {
418
- command,
443
+ command: redactSecrets(command),
419
444
  ok: false,
420
- stdout: error.stdout ? String(error.stdout) : '',
421
- stderr: error.stderr ? String(error.stderr) : '',
445
+ stdout: redactSecrets(error.stdout ? String(error.stdout) : ''),
446
+ stderr: redactSecrets(error.stderr ? String(error.stderr) : ''),
422
447
  exitCode: Number.isInteger(error.status) ? error.status : 1,
423
448
  error,
424
449
  };
@@ -428,7 +453,13 @@ function runValidationCommand(command, repoRoot) {
428
453
  function runValidationCommands(repoRoot, commands, runner = runValidationCommand) {
429
454
  const results = [];
430
455
  for (const command of commands) {
431
- const result = runner(command, repoRoot);
456
+ const rawResult = runner(command, repoRoot);
457
+ const result = {
458
+ ...rawResult,
459
+ command: redactSecrets(rawResult.command || command),
460
+ stderr: redactSecrets(rawResult.stderr || ''),
461
+ stdout: redactSecrets(rawResult.stdout || ''),
462
+ };
432
463
  results.push(result);
433
464
  if (!result.ok) {
434
465
  const details = [
@@ -492,11 +523,196 @@ function commitSliceChanges(repoRoot, slice, changedFiles, options = {}) {
492
523
  };
493
524
  }
494
525
 
526
+ function assertCorrectSliceWorktree(repoRoot, slice, options = {}) {
527
+ if (options.skipWorktreeBranchCheck === true) {
528
+ return null;
529
+ }
530
+
531
+ const expectedBranch = String(slice.branchName || slice.json.git?.branch_name || '').trim();
532
+ if (!expectedBranch) {
533
+ return null;
534
+ }
535
+
536
+ const actualBranch = currentBranch(repoRoot);
537
+ if (actualBranch !== expectedBranch) {
538
+ const error = new Error(formatError(`ai execute-slice must run from the slice worktree branch. Current branch: ${actualBranch || '(detached or unavailable)'}. Expected: ${expectedBranch}.`));
539
+ error.code = 'WRONG_WORKTREE';
540
+ error.details = {
541
+ actualBranch,
542
+ expectedBranch,
543
+ slice: slice.sliceRel,
544
+ };
545
+ throw error;
546
+ }
547
+
548
+ return {
549
+ actualBranch,
550
+ expectedBranch,
551
+ };
552
+ }
553
+
554
+ function sliceLifecycleArtifactPaths(repoRoot, slice) {
555
+ const closureAbs = path.join(path.dirname(slice.sliceAbs), 'CLOSURE_BRIEF.md');
556
+ return {
557
+ closure: toRelativePath(repoRoot, closureAbs),
558
+ commandLog: toRelativePath(repoRoot, path.join(slice.specDirAbs, 'COMMAND_LOG.md')),
559
+ evidence: toRelativePath(repoRoot, path.join(slice.specDirAbs, 'EVIDENCE_REPORT.md')),
560
+ sliceJson: toRelativePath(repoRoot, slice.sliceAbs),
561
+ status: toRelativePath(repoRoot, path.join(slice.specDirAbs, 'STATUS.md')),
562
+ };
563
+ }
564
+
565
+ function renderClosureBrief({ slice, changedFiles, validationResults, completedAt }) {
566
+ const criteria = Array.isArray(slice.acceptance) ? slice.acceptance : [];
567
+ const validationLines = Array.isArray(validationResults) && validationResults.length > 0
568
+ ? validationResults.map((result) => `- [x] \`${result.command}\` exited ${result.exitCode}`)
569
+ : ['- [x] No validation commands declared.'];
570
+
571
+ return `${[
572
+ `# CLOSURE BRIEF - ${slice.sliceId}: ${slice.json.title || slice.sliceId}`,
573
+ '',
574
+ '## Summary of Work',
575
+ '',
576
+ `Executed controlled slice closure at ${completedAt}. Quiver validated scope, validation commands, and lifecycle evidence for this slice.`,
577
+ '',
578
+ '## Validation Against Acceptance Criteria',
579
+ '',
580
+ ...(criteria.length > 0 ? criteria.map((item) => `- [x] ${item}`) : ['- [x] Slice execution completed with scope validation.']),
581
+ '',
582
+ '## Relevant Changes',
583
+ '',
584
+ ...formatList(changedFiles),
585
+ '',
586
+ '## Validation Commands',
587
+ '',
588
+ ...validationLines,
589
+ '',
590
+ '## Pending',
591
+ '',
592
+ 'None recorded by Quiver.',
593
+ '',
594
+ '## Remaining Risks',
595
+ '',
596
+ 'None recorded by Quiver.',
597
+ '',
598
+ '## Future Recommendations',
599
+ '',
600
+ 'Review the evidence report and commit diff before opening the PR.',
601
+ '',
602
+ ].join('\n')}\n`;
603
+ }
604
+
605
+ function appendSection(filePath, fallbackTitle, section) {
606
+ const current = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8').trimEnd() : fallbackTitle;
607
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
608
+ fs.writeFileSync(filePath, `${current}\n\n${section.trimEnd()}\n`);
609
+ }
610
+
611
+ function updateStatusMarkdown(filePath, slice, completedAt) {
612
+ const fallback = `# Status - ${slice.specSlug}\n`;
613
+ let text = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : fallback;
614
+ const rowRegex = new RegExp(`(\\|\\s*${escapeRegex(slice.sliceId)}\\s*\\|\\s*)[^|\\n]+(\\|[^\\n]*\\|)`);
615
+ if (rowRegex.test(text)) {
616
+ text = text.replace(rowRegex, '$1Completed $2');
617
+ }
618
+ text = text.replace(/\*\*Current slice:\*\*\s*[^\n]*/i, `**Current slice:** ${slice.sliceId} completed`);
619
+ if (!text.endsWith('\n')) {
620
+ text += '\n';
621
+ }
622
+ const section = [
623
+ '',
624
+ `## Execution Update - ${slice.sliceId}`,
625
+ '',
626
+ `- Status: Completed`,
627
+ `- Completed at: ${completedAt}`,
628
+ `- Source: \`npx create-quiver ai execute-slice --slice ${slice.sliceRel}\``,
629
+ ].join('\n');
630
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
631
+ fs.writeFileSync(filePath, `${text}${section}\n`);
632
+ }
633
+
634
+ function updateSliceJson(filePath, completedAt) {
635
+ const json = JSON.parse(fs.readFileSync(filePath, 'utf8'));
636
+ json.status = 'completed';
637
+ json.completed_at = completedAt;
638
+ fs.writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`);
639
+ }
640
+
641
+ function writeExecutionArtifacts(repoRoot, executorContext, details) {
642
+ const { slice } = executorContext;
643
+ const completedAt = details.completedAt || new Date().toISOString();
644
+ const artifacts = sliceLifecycleArtifactPaths(repoRoot, slice);
645
+ const changedFiles = uniqueList(details.changedFiles);
646
+ const closurePath = path.join(repoRoot, artifacts.closure);
647
+ const evidencePath = path.join(repoRoot, artifacts.evidence);
648
+ const commandLogPath = path.join(repoRoot, artifacts.commandLog);
649
+ const statusPath = path.join(repoRoot, artifacts.status);
650
+ const validationResults = Array.isArray(details.validationResults) ? details.validationResults : [];
651
+ const providerStdout = truncateText(redactSecrets(details.providerOutput?.stdout || ''), 1200).text;
652
+ const providerStderr = truncateText(redactSecrets(details.providerOutput?.stderr || ''), 1200).text;
653
+
654
+ fs.mkdirSync(path.dirname(closurePath), { recursive: true });
655
+ fs.writeFileSync(closurePath, renderClosureBrief({
656
+ slice,
657
+ changedFiles,
658
+ validationResults,
659
+ completedAt,
660
+ }));
661
+
662
+ const validationLines = validationResults.length > 0
663
+ ? validationResults.map((result) => `- \`${result.command}\` -> exit ${result.exitCode}`)
664
+ : ['- No validation commands declared.'];
665
+ appendSection(evidencePath, `# Evidence Report - ${slice.specSlug}`, [
666
+ `## ${slice.sliceId} - Execution Evidence`,
667
+ '',
668
+ `- Completed at: ${completedAt}`,
669
+ `- Changed files: ${changedFiles.length}`,
670
+ ...changedFiles.map((file) => ` - \`${file}\``),
671
+ `- Scope validation: passed`,
672
+ `- Provider stdout redacted: ${providerStdout ? 'yes' : 'n/a'}`,
673
+ `- Provider stderr redacted: ${providerStderr ? 'yes' : 'n/a'}`,
674
+ '',
675
+ '### Validation',
676
+ '',
677
+ ...validationLines,
678
+ '',
679
+ '### Provider Output',
680
+ '',
681
+ '```text',
682
+ providerStdout || 'n/a',
683
+ providerStderr ? `\n${providerStderr}` : '',
684
+ '```',
685
+ ].join('\n'));
686
+
687
+ const commandLogRows = [
688
+ `| ${completedAt} | ${slice.sliceId} | \`npx create-quiver ai execute-slice --slice ${slice.sliceRel}\` | passed |`,
689
+ ...validationResults.map((result) => `| ${completedAt} | ${slice.sliceId} | \`${result.command}\` | exit ${result.exitCode} |`),
690
+ ];
691
+ const commandLogHeader = [
692
+ '# Command Log',
693
+ '',
694
+ '| Timestamp | Slice | Command | Result |',
695
+ '|---|---|---|---|',
696
+ ].join('\n');
697
+ const currentCommandLog = fs.existsSync(commandLogPath) ? fs.readFileSync(commandLogPath, 'utf8').trimEnd() : commandLogHeader;
698
+ fs.mkdirSync(path.dirname(commandLogPath), { recursive: true });
699
+ fs.writeFileSync(commandLogPath, `${currentCommandLog}\n${commandLogRows.join('\n')}\n`);
700
+
701
+ updateStatusMarkdown(statusPath, slice, completedAt);
702
+ updateSliceJson(path.join(repoRoot, artifacts.sliceJson), completedAt);
703
+
704
+ return {
705
+ completedAt,
706
+ files: Object.values(artifacts),
707
+ };
708
+ }
709
+
495
710
  async function runExecuteSlice(repoRoot, options = {}) {
711
+ const canonicalRepoRoot = canonicalizeRepoRoot(repoRoot);
496
712
  const role = normalizeRole(options.role || DEFAULT_EXECUTE_ROLE);
497
713
  const provider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false)
498
714
  ? String(options.provider || DEFAULT_EXECUTE_PROVIDER).trim().toLowerCase()
499
- : resolveProfileProvider(repoRoot, role, DEFAULT_EXECUTE_PROVIDER);
715
+ : resolveProfileProvider(canonicalRepoRoot, role, DEFAULT_EXECUTE_PROVIDER);
500
716
  const context = options.context || DEFAULT_EXECUTE_CONTEXT;
501
717
  const timeoutMs = normalizeTimeout(options.timeout);
502
718
 
@@ -505,7 +721,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
505
721
  }
506
722
 
507
723
  const executorContext = buildExecuteSliceContext({
508
- repoRoot,
724
+ repoRoot: canonicalRepoRoot,
509
725
  slicePath: options.slice,
510
726
  role,
511
727
  context,
@@ -517,7 +733,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
517
733
  try {
518
734
  invocation = buildProviderInvocation(provider, {
519
735
  prompt,
520
- cwd: repoRoot,
736
+ cwd: canonicalRepoRoot,
521
737
  timeoutMs,
522
738
  });
523
739
  } catch (error) {
@@ -551,7 +767,16 @@ async function runExecuteSlice(repoRoot, options = {}) {
551
767
  return report;
552
768
  }
553
769
 
554
- const beforeSnapshot = captureWorktreeSnapshot(repoRoot);
770
+ try {
771
+ assertCorrectSliceWorktree(canonicalRepoRoot, executorContext.slice, options);
772
+ } catch (error) {
773
+ throw appendRecovery(error, executorContext.slice);
774
+ }
775
+
776
+ const beforeSnapshot = captureWorktreeSnapshot(canonicalRepoRoot);
777
+ if (beforeSnapshot.files.length > 0 && options.commit === true) {
778
+ throw appendRecovery(new Error(formatError(`ai execute-slice --commit requires a clean worktree before running. Commit or stash first: ${beforeSnapshot.files.join(', ')}`)), executorContext.slice);
779
+ }
555
780
  if (beforeSnapshot.files.length > 0 && options.allowDirty !== true) {
556
781
  throw appendRecovery(new Error(formatError(`ai execute-slice requires a clean worktree before running. Commit or stash first: ${beforeSnapshot.files.join(', ')}`)), executorContext.slice);
557
782
  }
@@ -560,7 +785,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
560
785
  try {
561
786
  result = await (options.runProviderFn || runProvider)(provider, {
562
787
  prompt,
563
- cwd: repoRoot,
788
+ cwd: canonicalRepoRoot,
564
789
  timeoutMs,
565
790
  dryRun: false,
566
791
  probe: options.probe,
@@ -574,17 +799,27 @@ async function runExecuteSlice(repoRoot, options = {}) {
574
799
  }
575
800
 
576
801
  if (result.stdout) {
577
- process.stdout.write(result.stdout);
802
+ process.stdout.write(redactSecrets(result.stdout));
578
803
  }
579
804
  if (result.stderr) {
580
- process.stderr.write(result.stderr);
805
+ process.stderr.write(redactSecrets(result.stderr));
581
806
  }
582
807
 
583
808
  if (!result.ok) {
584
809
  throw appendRecovery(annotateProviderError(result.error || new Error('provider run failed'), 'execute-slice'), executorContext.slice);
585
810
  }
586
811
 
587
- const afterSnapshot = captureWorktreeSnapshot(repoRoot);
812
+ const providerOutput = {
813
+ stdout: redactSecrets(result.stdout || ''),
814
+ stderr: redactSecrets(result.stderr || ''),
815
+ };
816
+ const sanitizedResult = {
817
+ ...result,
818
+ stdout: providerOutput.stdout,
819
+ stderr: providerOutput.stderr,
820
+ };
821
+
822
+ const afterSnapshot = captureWorktreeSnapshot(canonicalRepoRoot);
588
823
  let scopeResult;
589
824
  try {
590
825
  scopeResult = validateScopeSnapshot({
@@ -596,11 +831,16 @@ async function runExecuteSlice(repoRoot, options = {}) {
596
831
  } catch (error) {
597
832
  throw appendRecovery(error, executorContext.slice);
598
833
  }
834
+ if (scopeResult.changedFiles.length === 0) {
835
+ const error = new Error(formatError('provider produced no changed files; slice closure was not updated.'));
836
+ error.code = 'NO_CHANGES_TO_CLOSE';
837
+ throw appendRecovery(error, executorContext.slice);
838
+ }
599
839
 
600
840
  let validationResults = [];
601
841
  try {
602
842
  validationResults = runValidationCommands(
603
- repoRoot,
843
+ canonicalRepoRoot,
604
844
  executorContext.validationCommands,
605
845
  options.runValidationCommandFn,
606
846
  );
@@ -608,11 +848,18 @@ async function runExecuteSlice(repoRoot, options = {}) {
608
848
  throw appendRecovery(error, executorContext.slice);
609
849
  }
610
850
 
611
- const finalSnapshot = captureWorktreeSnapshot(repoRoot);
851
+ const artifacts = writeExecutionArtifacts(canonicalRepoRoot, executorContext, {
852
+ changedFiles: scopeResult.changedFiles,
853
+ completedAt: new Date().toISOString(),
854
+ providerOutput,
855
+ validationResults,
856
+ });
857
+
858
+ const finalSnapshot = captureWorktreeSnapshot(canonicalRepoRoot);
612
859
  let finalScopeResult;
613
860
  try {
614
861
  finalScopeResult = validateScopeSnapshot({
615
- allowedFiles: executorContext.allowedFiles,
862
+ allowedFiles: uniqueList([...executorContext.allowedFiles, ...artifacts.files]),
616
863
  beforeSnapshot,
617
864
  afterSnapshot: finalSnapshot,
618
865
  strict: true,
@@ -624,7 +871,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
624
871
  let commitResult = null;
625
872
  if (options.commit === true) {
626
873
  try {
627
- commitResult = commitSliceChanges(repoRoot, executorContext.slice, finalScopeResult.changedFiles, {
874
+ commitResult = commitSliceChanges(canonicalRepoRoot, executorContext.slice, finalScopeResult.changedFiles, {
628
875
  message: options.commitMessage,
629
876
  });
630
877
  } catch (error) {
@@ -649,12 +896,13 @@ async function runExecuteSlice(repoRoot, options = {}) {
649
896
  slice: executorContext.slice.sliceId,
650
897
  specSlug: executorContext.slice.specSlug,
651
898
  invocation,
652
- result,
899
+ result: sanitizedResult,
653
900
  beforeSnapshot,
654
901
  afterSnapshot: finalSnapshot,
655
902
  scopeResult: finalScopeResult,
656
903
  validationResults,
657
904
  commitResult,
905
+ artifacts,
658
906
  };
659
907
  }
660
908
 
@@ -672,6 +920,8 @@ module.exports = {
672
920
  commitSliceChanges,
673
921
  formatExecuteSliceDryRunReport,
674
922
  formatExecuteSliceResult,
923
+ assertCorrectSliceWorktree,
924
+ writeExecutionArtifacts,
675
925
  runValidationCommand,
676
926
  runValidationCommands,
677
927
  normalizeTimeout,