create-quiver 0.12.0 → 0.13.0

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 (158) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +65 -25
  3. package/README_FOR_AI.md +36 -29
  4. package/ROADMAP.md +22 -3
  5. package/docs/AI_ONBOARDING_PROMPT.md.template +7 -1
  6. package/docs/COMMANDS.md.template +53 -20
  7. package/docs/STATUS.md.template +5 -1
  8. package/docs/WORKFLOW.md.template +13 -11
  9. package/package.json +10 -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/specs/quiver-v27-reliability-ai-workflow-hardening/AUDIT_V24_V25_V26.md +67 -0
  81. package/specs/quiver-v27-reliability-ai-workflow-hardening/COMMAND_CONTRACTS.md +125 -0
  82. package/specs/quiver-v27-reliability-ai-workflow-hardening/COVERAGE_MATRIX.md +74 -0
  83. package/specs/quiver-v27-reliability-ai-workflow-hardening/EVIDENCE_REPORT.md +179 -0
  84. package/specs/quiver-v27-reliability-ai-workflow-hardening/EXECUTION_PLAN.md +71 -0
  85. package/specs/quiver-v27-reliability-ai-workflow-hardening/SPEC.md +176 -0
  86. package/specs/quiver-v27-reliability-ai-workflow-hardening/STATUS.md +37 -0
  87. package/specs/quiver-v27-reliability-ai-workflow-hardening/pr.md +132 -0
  88. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/CLOSURE_BRIEF.md +36 -0
  89. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/EXECUTION_BRIEF.md +56 -0
  90. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/slice.json +75 -0
  91. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/CLOSURE_BRIEF.md +37 -0
  92. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/EXECUTION_BRIEF.md +54 -0
  93. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/slice.json +79 -0
  94. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/CLOSURE_BRIEF.md +34 -0
  95. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/EXECUTION_BRIEF.md +54 -0
  96. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/slice.json +75 -0
  97. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/CLOSURE_BRIEF.md +36 -0
  98. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/EXECUTION_BRIEF.md +55 -0
  99. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/slice.json +78 -0
  100. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/CLOSURE_BRIEF.md +31 -0
  101. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/EXECUTION_BRIEF.md +55 -0
  102. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/slice.json +77 -0
  103. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/CLOSURE_BRIEF.md +31 -0
  104. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/EXECUTION_BRIEF.md +55 -0
  105. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/slice.json +84 -0
  106. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/CLOSURE_BRIEF.md +32 -0
  107. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/EXECUTION_BRIEF.md +57 -0
  108. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/slice.json +99 -0
  109. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/CLOSURE_BRIEF.md +31 -0
  110. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/EXECUTION_BRIEF.md +57 -0
  111. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/slice.json +88 -0
  112. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/CLOSURE_BRIEF.md +31 -0
  113. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/EXECUTION_BRIEF.md +56 -0
  114. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/slice.json +85 -0
  115. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/CLOSURE_BRIEF.md +32 -0
  116. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/EXECUTION_BRIEF.md +56 -0
  117. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/slice.json +91 -0
  118. package/src/create-quiver/commands/ai.js +652 -27
  119. package/src/create-quiver/commands/flow.js +58 -9
  120. package/src/create-quiver/commands/graph.js +11 -9
  121. package/src/create-quiver/commands/plan.js +7 -16
  122. package/src/create-quiver/commands/spec.js +282 -0
  123. package/src/create-quiver/index.js +409 -31
  124. package/src/create-quiver/lib/actionable-error.js +27 -0
  125. package/src/create-quiver/lib/agent-profiles.js +16 -4
  126. package/src/create-quiver/lib/ai/artifacts.js +318 -0
  127. package/src/create-quiver/lib/ai/context-packs.js +4 -0
  128. package/src/create-quiver/lib/ai/execution-plan.js +16 -1
  129. package/src/create-quiver/lib/ai/executor.js +272 -21
  130. package/src/create-quiver/lib/ai/export-state.js +679 -0
  131. package/src/create-quiver/lib/ai/github.js +162 -2
  132. package/src/create-quiver/lib/ai/onboarding-template.js +215 -2
  133. package/src/create-quiver/lib/ai/plan-review.js +7 -2
  134. package/src/create-quiver/lib/ai/providers.js +4 -3
  135. package/src/create-quiver/lib/ai/run-state.js +414 -0
  136. package/src/create-quiver/lib/ai/spec-generator.js +84 -13
  137. package/src/create-quiver/lib/ai/spec-templates.js +150 -21
  138. package/src/create-quiver/lib/analyze.js +2 -2
  139. package/src/create-quiver/lib/approvals.js +36 -5
  140. package/src/create-quiver/lib/demo.js +189 -14
  141. package/src/create-quiver/lib/doctor.js +154 -0
  142. package/src/create-quiver/lib/git.js +40 -1
  143. package/src/create-quiver/lib/handoff.js +123 -12
  144. package/src/create-quiver/lib/init-docs.js +35 -13
  145. package/src/create-quiver/lib/init-layout.js +9 -0
  146. package/src/create-quiver/lib/json.js +53 -3
  147. package/src/create-quiver/lib/lifecycle.js +52 -3
  148. package/src/create-quiver/lib/locks.js +134 -0
  149. package/src/create-quiver/lib/package-safety.js +7 -0
  150. package/src/create-quiver/lib/paths.js +74 -0
  151. package/src/create-quiver/lib/project-scan.js +74 -0
  152. package/src/create-quiver/lib/project-state-resolver.js +236 -0
  153. package/src/create-quiver/lib/readiness.js +66 -10
  154. package/src/create-quiver/lib/scope.js +52 -8
  155. package/src/create-quiver/lib/slice-graph.js +138 -38
  156. package/src/create-quiver/lib/slice.js +14 -5
  157. package/src/create-quiver/lib/spec-worktrees.js +129 -32
  158. package/src/create-quiver/lib/statuses.js +115 -0
@@ -4,6 +4,7 @@ const path = require('path');
4
4
  const { readPhaseApproval } = require('../lib/approvals');
5
5
  const { readPlanReview } = require('../lib/ai/plan-review');
6
6
  const { listAgentProfiles } = require('../lib/agent-profiles');
7
+ const { readProjectScanStatus } = require('../lib/project-scan');
7
8
  const { buildGraph, naturalNumberFromSliceId, readAllSlices } = require('../lib/slice-graph');
8
9
  const { hasQuiverInitializationEvidence, readState } = require('../lib/state');
9
10
 
@@ -11,6 +12,46 @@ function exists(projectRoot, relativePath) {
11
12
  return fs.existsSync(path.join(projectRoot, relativePath));
12
13
  }
13
14
 
15
+ function readJsonIfExists(filePath) {
16
+ if (!fs.existsSync(filePath)) {
17
+ return null;
18
+ }
19
+
20
+ try {
21
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
22
+ } catch {
23
+ return null;
24
+ }
25
+ }
26
+
27
+ function detectPackageManager(projectRoot) {
28
+ const packageManagerField = readJsonIfExists(path.join(projectRoot, 'package.json'))?.packageManager;
29
+ if (typeof packageManagerField === 'string' && packageManagerField.trim()) {
30
+ return packageManagerField.split('@')[0];
31
+ }
32
+
33
+ const signals = [
34
+ ['bun', 'bun.lockb'],
35
+ ['bun', 'bun.lock'],
36
+ ['pnpm', 'pnpm-lock.yaml'],
37
+ ['yarn', 'yarn.lock'],
38
+ ['npm', 'package-lock.json'],
39
+ ];
40
+
41
+ for (const [manager, filename] of signals) {
42
+ if (exists(projectRoot, filename)) {
43
+ return manager;
44
+ }
45
+ }
46
+
47
+ return 'npm';
48
+ }
49
+
50
+ function formatRunScriptCommand(packageManager, scriptName) {
51
+ const manager = ['npm', 'pnpm', 'yarn', 'bun'].includes(packageManager) ? packageManager : 'npm';
52
+ return `${manager} run ${scriptName}`;
53
+ }
54
+
14
55
  function listSpecSlugs(projectRoot) {
15
56
  const specsDir = path.join(projectRoot, 'specs');
16
57
  if (!fs.existsSync(specsDir)) {
@@ -53,6 +94,7 @@ function summarizeDocs(projectRoot) {
53
94
  hasProjectMap: exists(projectRoot, 'docs/PROJECT_MAP.md'),
54
95
  hasAiContext: exists(projectRoot, 'docs/AI_CONTEXT.md'),
55
96
  hasOnboardingPrompt: exists(projectRoot, 'docs/AI_ONBOARDING_PROMPT.md'),
97
+ scanStatus: readProjectScanStatus(projectRoot),
56
98
  };
57
99
  const missing = [
58
100
  ['docs/PROJECT_MAP.md', docs.hasProjectMap],
@@ -77,7 +119,7 @@ function summarizeAgentProfiles(projectRoot) {
77
119
  };
78
120
  }
79
121
 
80
- function buildFacts({ initialized, docs, approvals, planReview, agents, specSlugs, state, slices = null }) {
122
+ function buildFacts({ initialized, docs, approvals, planReview, agents, packageManager, specSlugs, state, slices = null }) {
81
123
  return {
82
124
  initialized,
83
125
  hasProjectMap: docs.hasProjectMap,
@@ -91,6 +133,9 @@ function buildFacts({ initialized, docs, approvals, planReview, agents, specSlug
91
133
  agents,
92
134
  specSlugs,
93
135
  slices,
136
+ contextSource: docs.scanStatus,
137
+ packageManager,
138
+ flowScriptCommand: formatRunScriptCommand(packageManager, 'quiver:flow'),
94
139
  quiverVersion: state?.quiver_version || null,
95
140
  };
96
141
  }
@@ -207,8 +252,9 @@ function detectFlowState(projectRoot) {
207
252
  };
208
253
  const planReview = safeReadPlanReview(projectRoot);
209
254
  const agents = summarizeAgentProfiles(projectRoot);
255
+ const packageManager = detectPackageManager(projectRoot);
210
256
  const specSlugs = listSpecSlugs(projectRoot);
211
- const facts = buildFacts({ initialized, docs, approvals, planReview, agents, specSlugs, state });
257
+ const facts = buildFacts({ initialized, docs, approvals, planReview, agents, packageManager, specSlugs, state });
212
258
 
213
259
  if (!initialized) {
214
260
  return baseReport({
@@ -289,10 +335,11 @@ function detectFlowState(projectRoot) {
289
335
  stage: 'criteria-draft',
290
336
  label: 'acceptance criteria need approval',
291
337
  blockers: ['Acceptance criteria draft exists but is not approved.'],
292
- nextCommand: 'npx create-quiver ai approve --phase acceptance --input acceptance-approved.md',
338
+ nextCommand: 'npx create-quiver ai approve --phase acceptance --version <n>',
293
339
  suggestedCommands: [
294
340
  'npx create-quiver ai approvals',
295
- 'npx create-quiver ai approve --phase acceptance --input acceptance-approved.md',
341
+ 'npx create-quiver ai revise --phase acceptance --input feedback.md --dry-run',
342
+ 'npx create-quiver ai approve --phase acceptance --version <n>',
296
343
  ],
297
344
  facts,
298
345
  });
@@ -303,10 +350,11 @@ function detectFlowState(projectRoot) {
303
350
  stage: 'criteria-stale',
304
351
  label: 'acceptance criteria approval is stale',
305
352
  blockers: ['Acceptance criteria changed after approval.'],
306
- nextCommand: 'npx create-quiver ai approve --phase acceptance --input acceptance-approved.md',
353
+ nextCommand: 'npx create-quiver ai approve --phase acceptance --version <n>',
307
354
  suggestedCommands: [
308
355
  'npx create-quiver ai approvals',
309
- 'npx create-quiver ai approve --phase acceptance --input acceptance-approved.md',
356
+ 'npx create-quiver ai revise --phase acceptance --input feedback.md --dry-run',
357
+ 'npx create-quiver ai approve --phase acceptance --version <n>',
310
358
  ],
311
359
  facts,
312
360
  });
@@ -407,7 +455,6 @@ function detectFlowState(projectRoot) {
407
455
  suggestedCommands: [
408
456
  'npx create-quiver ai review-plan --dry-run',
409
457
  'npx create-quiver ai review-plan',
410
- 'npx create-quiver ai approve --phase technical-plan --version <n>',
411
458
  ],
412
459
  facts,
413
460
  });
@@ -429,7 +476,7 @@ function detectFlowState(projectRoot) {
429
476
  }
430
477
 
431
478
  const slices = summarizeSlices(projectRoot, specSlugs);
432
- const sliceFacts = buildFacts({ initialized, docs, approvals, planReview, agents, specSlugs, state, slices: {
479
+ const sliceFacts = buildFacts({ initialized, docs, approvals, planReview, agents, packageManager, specSlugs, state, slices: {
433
480
  completed: slices.completedCount,
434
481
  pending: slices.pendingCount,
435
482
  ready: slices.ready.map((slice) => slice.ref),
@@ -515,10 +562,12 @@ function formatFlowReport(report) {
515
562
  'Command path:',
516
563
  '- Bootstrap and remote use: npx create-quiver <command>',
517
564
  '- Short alias after local install: quiver <command>',
518
- '- Generated npm script: npm run quiver:flow',
565
+ `- Generated project script: ${report.facts.flowScriptCommand}`,
519
566
  '',
567
+ `Package manager: ${report.facts.packageManager}`,
520
568
  `Stage: ${report.label}`,
521
569
  `Next safe command: ${report.nextCommand}`,
570
+ `Context source: ${report.facts.contextSource.summary}`,
522
571
  ];
523
572
 
524
573
  if (report.blockers.length > 0) {
@@ -1,11 +1,9 @@
1
- const { buildGraph, computeLevels, detectFileConflicts, readAllSlices } = require('../lib/slice-graph');
1
+ const { filterSlicesForExecution, resolveProjectState } = require('../lib/project-state-resolver');
2
+ const { computeLevels, detectFileConflicts } = require('../lib/slice-graph');
2
3
  const { renderDotGraph } = require('../lib/renderers/dot');
3
4
  const { renderMermaidGraph } = require('../lib/renderers/mermaid');
4
5
  const { renderTreeGraph, isUnicodeEnabled } = require('../lib/renderers/tree');
5
6
 
6
- const EXCLUDED_STATUSES = new Set(['completed', 'skipped', 'cancelled']);
7
- const HISTORY_EXCLUDED_STATUSES = new Set(['skipped', 'cancelled']);
8
-
9
7
  function toGraphNode(node) {
10
8
  return {
11
9
  ref: node.ref,
@@ -14,6 +12,7 @@ function toGraphNode(node) {
14
12
  title: node.title || node.sliceId,
15
13
  hours: Number.isFinite(Number(node.json?.estimated_hours)) ? Number(node.json.estimated_hours) : 0,
16
14
  status: node.status || 'draft',
15
+ canonical_status: node.canonical_status || 'planned',
17
16
  files: Array.isArray(node.files) ? node.files : [],
18
17
  depends_on: Array.isArray(node.depends_on) ? node.depends_on : [],
19
18
  };
@@ -28,13 +27,16 @@ function buildConflictPayload(levelIndex, groups) {
28
27
  }
29
28
 
30
29
  function collectGraph(repoRoot, options = {}) {
31
- const graph = buildGraph(readAllSlices(repoRoot));
32
- computeLevels(graph);
33
- const includeCompleted = options.includeCompleted === true;
34
- const excluded = includeCompleted ? HISTORY_EXCLUDED_STATUSES : EXCLUDED_STATUSES;
35
30
  const specSlug = options.specSlug ? String(options.specSlug).trim() : '';
31
+ const state = resolveProjectState(repoRoot, { specSlug });
32
+ const graph = state.graph;
33
+ if (!specSlug) {
34
+ computeLevels(graph);
35
+ }
36
+ const includeCompleted = options.includeCompleted === true;
37
+ const executionRefs = new Set(filterSlicesForExecution(graph.nodes, { includeCompleted }).map((node) => node.ref));
36
38
  const pendingNodes = graph.nodes.filter((node) => {
37
- if (excluded.has(String(node.status || '').toLowerCase())) {
39
+ if (!executionRefs.has(node.ref)) {
38
40
  return false;
39
41
  }
40
42
  if (specSlug && node.specSlug !== specSlug) {
@@ -1,10 +1,6 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
1
  const { relativePosixPath } = require('../lib/paths');
4
- const { buildGraph, readAllSlices, topoSort } = require('../lib/slice-graph');
5
-
6
- const EXCLUDED_STATUSES = new Set(['completed', 'skipped', 'cancelled']);
7
- const HISTORY_EXCLUDED_STATUSES = new Set(['skipped', 'cancelled']);
2
+ const { filterSlicesForExecution, resolveProjectState } = require('../lib/project-state-resolver');
3
+ const { topoSort } = require('../lib/slice-graph');
8
4
 
9
5
  function toHourCount(value) {
10
6
  const parsed = Number(value);
@@ -26,6 +22,7 @@ function sliceToPlanItem(repoRoot, slice, index, readySet) {
26
22
  ticket: slice.ticket || '',
27
23
  title: slice.title || slice.sliceId,
28
24
  status: slice.status || 'draft',
25
+ canonical_status: slice.canonical_status || 'planned',
29
26
  hours: toHourCount(slice.json?.estimated_hours),
30
27
  files: Array.isArray(slice.files) ? slice.files : [],
31
28
  depends_on: Array.isArray(slice.depends_on) ? slice.depends_on : [],
@@ -116,18 +113,12 @@ function buildCriticalPath(graph, refs) {
116
113
  }
117
114
 
118
115
  function collectPlan(repoRoot, options = {}) {
119
- const allSlices = readAllSlices(repoRoot);
120
- const graph = buildGraph(allSlices);
116
+ const specSlug = options.specSlug ? String(options.specSlug).trim() : '';
117
+ const state = resolveProjectState(repoRoot, { specSlug });
118
+ const graph = state.graph;
121
119
  const topo = topoSort(graph);
122
120
  const includeCompleted = options.includeCompleted === true;
123
- const excluded = includeCompleted ? HISTORY_EXCLUDED_STATUSES : EXCLUDED_STATUSES;
124
- const specSlug = options.specSlug ? String(options.specSlug).trim() : '';
125
-
126
- const pendingRefs = new Set(
127
- graph.nodes
128
- .filter((node) => !excluded.has(String(node.status || '').toLowerCase()))
129
- .map((node) => node.ref),
130
- );
121
+ const pendingRefs = new Set(filterSlicesForExecution(graph.nodes, { includeCompleted }).map((node) => node.ref));
131
122
 
132
123
  const readyRefs = buildReadySet(graph, Array.from(pendingRefs));
133
124
 
@@ -1,6 +1,9 @@
1
1
  const fs = require('node:fs');
2
2
  const path = require('node:path');
3
3
 
4
+ const { checkHandoff } = require('../lib/handoff');
5
+ const { parseJsonWithComments } = require('../lib/json');
6
+ const { assertPathInsideRoot, validateProjectRelativePaths } = require('../lib/paths');
4
7
  const { resolveReviewedTechnicalPlanInput } = require('../lib/ai/plan-review');
5
8
  const {
6
9
  buildSpecGenerationManifest,
@@ -24,6 +27,282 @@ function readInputText(repoRoot, inputPath) {
24
27
  return fs.readFileSync(resolved, 'utf8');
25
28
  }
26
29
 
30
+ function resolveSpecDir(repoRoot, specInput) {
31
+ const value = String(specInput || '').trim();
32
+ if (!value) {
33
+ throw new Error(formatError('missing spec directory. Use: npx create-quiver spec validate specs/<spec-slug>'));
34
+ }
35
+
36
+ const candidates = [
37
+ path.resolve(repoRoot, value),
38
+ path.resolve(repoRoot, 'specs', value),
39
+ path.resolve(repoRoot, 'specs-fix', value),
40
+ ];
41
+
42
+ for (const candidate of candidates) {
43
+ if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {
44
+ assertPathInsideRoot(repoRoot, candidate, 'spec path');
45
+ return candidate;
46
+ }
47
+ }
48
+
49
+ throw new Error(formatError(`spec directory not found: ${value}`));
50
+ }
51
+
52
+ function findSliceJsonFiles(specDir) {
53
+ const slicesRoot = path.join(specDir, 'slices');
54
+ const files = [];
55
+ if (!fs.existsSync(slicesRoot)) {
56
+ return files;
57
+ }
58
+
59
+ const stack = [slicesRoot];
60
+ while (stack.length > 0) {
61
+ const current = stack.pop();
62
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
63
+ const fullPath = path.join(current, entry.name);
64
+ if (entry.isDirectory()) {
65
+ stack.push(fullPath);
66
+ } else if (entry.isFile() && entry.name === 'slice.json') {
67
+ files.push(fullPath);
68
+ }
69
+ }
70
+ }
71
+
72
+ return files.sort();
73
+ }
74
+
75
+ function shortSliceId(sliceId) {
76
+ const match = String(sliceId || '').match(/^(slice-\d+)/);
77
+ return match ? match[1] : String(sliceId || '');
78
+ }
79
+
80
+ function normalizeDependency(specSlug, sliceId, dep) {
81
+ const value = String(dep || '').trim();
82
+ if (!value) {
83
+ return '';
84
+ }
85
+ if (value.includes('/')) {
86
+ return value;
87
+ }
88
+ return `${specSlug}/${value}`;
89
+ }
90
+
91
+ function detectCycle(nodes) {
92
+ const visiting = new Set();
93
+ const visited = new Set();
94
+ const stack = [];
95
+
96
+ function visit(ref) {
97
+ if (visiting.has(ref)) {
98
+ const start = stack.indexOf(ref);
99
+ return [...stack.slice(start), ref];
100
+ }
101
+ if (visited.has(ref)) {
102
+ return null;
103
+ }
104
+
105
+ visiting.add(ref);
106
+ stack.push(ref);
107
+ const node = nodes.get(ref);
108
+ for (const dep of node?.deps || []) {
109
+ if (!nodes.has(dep)) {
110
+ continue;
111
+ }
112
+ const cycle = visit(dep);
113
+ if (cycle) {
114
+ return cycle;
115
+ }
116
+ }
117
+ stack.pop();
118
+ visiting.delete(ref);
119
+ visited.add(ref);
120
+ return null;
121
+ }
122
+
123
+ for (const ref of nodes.keys()) {
124
+ const cycle = visit(ref);
125
+ if (cycle) {
126
+ return cycle;
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+
132
+ function buildSpecValidationReport(repoRoot, specInput, options = {}) {
133
+ const strict = options.strict === true;
134
+ const specDir = resolveSpecDir(repoRoot, specInput);
135
+ const relativeSpecDir = toRelativePosix(repoRoot, specDir);
136
+ const specSlug = path.basename(specDir);
137
+ const errors = [];
138
+ const warnings = [];
139
+ const checked = [];
140
+ const docs = ['SPEC.md', 'STATUS.md', 'EVIDENCE_REPORT.md'];
141
+ const docText = {};
142
+
143
+ for (const doc of docs) {
144
+ const filePath = path.join(specDir, doc);
145
+ if (!fs.existsSync(filePath)) {
146
+ errors.push(`missing required spec document: ${relativeSpecDir}/${doc}`);
147
+ continue;
148
+ }
149
+ docText[doc] = fs.readFileSync(filePath, 'utf8');
150
+ checked.push(`${relativeSpecDir}/${doc}`);
151
+ }
152
+
153
+ const sliceFiles = findSliceJsonFiles(specDir);
154
+ if (sliceFiles.length === 0) {
155
+ errors.push(`spec has no slices: ${relativeSpecDir}/slices`);
156
+ }
157
+
158
+ const nodes = new Map();
159
+ const seenSliceIds = new Set();
160
+
161
+ for (const sliceFile of sliceFiles) {
162
+ const relativeSliceFile = toRelativePosix(repoRoot, sliceFile);
163
+ checked.push(relativeSliceFile);
164
+
165
+ let json;
166
+ try {
167
+ json = parseJsonWithComments(fs.readFileSync(sliceFile, 'utf8'));
168
+ } catch (error) {
169
+ errors.push(`invalid JSON in ${relativeSliceFile}: ${error.message}`);
170
+ continue;
171
+ }
172
+
173
+ const sliceId = String(json.slice_id || '').trim();
174
+ const expectedSliceId = path.basename(path.dirname(sliceFile));
175
+ if (!sliceId) {
176
+ errors.push(`${relativeSliceFile} is missing slice_id`);
177
+ continue;
178
+ }
179
+ if (sliceId !== expectedSliceId) {
180
+ errors.push(`${relativeSliceFile} slice_id must match directory name (${expectedSliceId})`);
181
+ }
182
+ if (seenSliceIds.has(sliceId)) {
183
+ errors.push(`duplicate slice_id in spec: ${sliceId}`);
184
+ }
185
+ seenSliceIds.add(sliceId);
186
+
187
+ const writeScope = Array.isArray(json.allowed_write_paths) && json.allowed_write_paths.length > 0
188
+ ? json.allowed_write_paths
189
+ : json.files;
190
+ if (!Array.isArray(writeScope) || writeScope.length === 0) {
191
+ errors.push(`${relativeSliceFile} must declare files or allowed_write_paths`);
192
+ }
193
+ try {
194
+ validateProjectRelativePaths(writeScope, `${relativeSliceFile} write scope`);
195
+ validateProjectRelativePaths(json.expected_read_paths, `${relativeSliceFile} expected_read_paths`);
196
+ } catch (error) {
197
+ errors.push(error.message);
198
+ }
199
+
200
+ for (const briefName of ['EXECUTION_BRIEF.md', 'CLOSURE_BRIEF.md']) {
201
+ const briefRel = toRelativePosix(repoRoot, path.join(path.dirname(sliceFile), briefName));
202
+ try {
203
+ checkHandoff(briefRel, repoRoot);
204
+ checked.push(briefRel);
205
+ } catch (error) {
206
+ errors.push(error.message);
207
+ }
208
+ }
209
+
210
+ const ref = `${specSlug}/${sliceId}`;
211
+ const deps = (Array.isArray(json.depends_on) ? json.depends_on : [])
212
+ .map((dep) => normalizeDependency(specSlug, sliceId, dep))
213
+ .filter(Boolean);
214
+ nodes.set(ref, { deps, json, ref, sliceId });
215
+
216
+ const statusNeedle = shortSliceId(sliceId);
217
+ if (docText['STATUS.md'] && !docText['STATUS.md'].includes(sliceId) && !docText['STATUS.md'].includes(statusNeedle)) {
218
+ warnings.push(`STATUS.md does not reference ${sliceId}`);
219
+ }
220
+
221
+ if (String(json.status || '').trim() === 'completed') {
222
+ if (!json.completed_at) {
223
+ warnings.push(`${relativeSliceFile} is completed but missing completed_at`);
224
+ }
225
+ if (docText['EVIDENCE_REPORT.md'] && !docText['EVIDENCE_REPORT.md'].includes(sliceId) && !docText['EVIDENCE_REPORT.md'].includes(statusNeedle)) {
226
+ warnings.push(`EVIDENCE_REPORT.md does not reference completed slice ${sliceId}`);
227
+ }
228
+ }
229
+ }
230
+
231
+ for (const [ref, node] of nodes.entries()) {
232
+ for (const dep of node.deps) {
233
+ if (dep.startsWith(`${specSlug}/`) && !nodes.has(dep)) {
234
+ errors.push(`${ref} depends on missing slice ${dep}`);
235
+ }
236
+ }
237
+ }
238
+
239
+ const cycle = detectCycle(nodes);
240
+ if (cycle) {
241
+ errors.push(`dependency cycle detected: ${cycle.join(' -> ')}`);
242
+ }
243
+
244
+ if (strict && warnings.length > 0) {
245
+ errors.push(...warnings.map((warning) => `strict warning: ${warning}`));
246
+ }
247
+
248
+ return {
249
+ ok: errors.length === 0,
250
+ specDir: relativeSpecDir,
251
+ checked,
252
+ errors,
253
+ warnings,
254
+ slices: sliceFiles.length,
255
+ strict,
256
+ };
257
+ }
258
+
259
+ function formatSpecValidationReport(report) {
260
+ const lines = [
261
+ 'Quiver spec validation',
262
+ `Spec: ${report.specDir}`,
263
+ `Slices: ${report.slices}`,
264
+ `Strict: ${report.strict ? 'yes' : 'no'}`,
265
+ ];
266
+
267
+ if (report.checked.length > 0) {
268
+ lines.push('Checked files:');
269
+ for (const file of report.checked) {
270
+ lines.push(`- ${file}`);
271
+ }
272
+ }
273
+
274
+ if (report.warnings.length > 0) {
275
+ lines.push('Warnings:');
276
+ for (const warning of report.warnings) {
277
+ lines.push(`- ${warning}`);
278
+ }
279
+ }
280
+
281
+ if (report.errors.length > 0) {
282
+ lines.push('Errors:');
283
+ for (const error of report.errors) {
284
+ lines.push(`- ${error}`);
285
+ }
286
+ }
287
+
288
+ lines.push(report.ok ? 'PASS: spec validation passed.' : 'FAIL: spec validation failed.');
289
+ return `${lines.join('\n')}\n`;
290
+ }
291
+
292
+ function runValidateSpec(repoRoot, specInput, options = {}) {
293
+ const report = buildSpecValidationReport(repoRoot, specInput, options);
294
+ process.stdout.write(formatSpecValidationReport(report));
295
+
296
+ if (!report.ok) {
297
+ const error = new Error(formatError(`spec validate failed for ${report.specDir}`));
298
+ error.code = 'SPEC_VALIDATE_FAILED';
299
+ error.details = report;
300
+ throw error;
301
+ }
302
+
303
+ return report;
304
+ }
305
+
27
306
  function buildSpecCreatePreview(repoRoot, options = {}) {
28
307
  const resolved = resolveReviewedTechnicalPlanInput(repoRoot, options.input || undefined);
29
308
  const inputPath = resolved.inputPath;
@@ -126,8 +405,11 @@ function runCreateSpec(repoRoot, options = {}) {
126
405
  }
127
406
 
128
407
  module.exports = {
408
+ buildSpecValidationReport,
129
409
  buildSpecCreatePreview,
130
410
  formatSpecCreateDryRun,
131
411
  formatSpecCreateResult,
412
+ formatSpecValidationReport,
132
413
  runCreateSpec,
414
+ runValidateSpec,
133
415
  };