create-quiver 0.7.0 → 0.8.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 (83) hide show
  1. package/.claude/settings.local.json +45 -0
  2. package/.github/workflows/ci.yml +2 -2
  3. package/BACKLOG.md +139 -0
  4. package/CHANGELOG.md +13 -1
  5. package/README.md +31 -1
  6. package/README_FOR_AI.md +25 -13
  7. package/ROADMAP.md +28 -6
  8. package/docs/AI_ONBOARDING_PROMPT.md.template +4 -0
  9. package/docs/COMMANDS.md.template +25 -0
  10. package/docs/INDEX.md.template +2 -0
  11. package/docs/SUPPORT_MATRIX.md.template +9 -0
  12. package/docs/WORKFLOW.md.template +4 -0
  13. package/docs/examples/graph.md.template +62 -0
  14. package/docs/examples/next.md.template +27 -0
  15. package/docs/examples/plan.md.template +28 -0
  16. package/package.json +5 -2
  17. package/package.template.json +8 -3
  18. package/scripts/check-slice-readiness.sh +6 -4
  19. package/scripts/init-docs.sh +57 -9
  20. package/specs/[project-name]/HANDOFF.md.template +37 -0
  21. package/specs/[project-name]/slices/slice-template/slice.json +5 -0
  22. package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-01-project-scan-command/slice.json +1 -1
  23. package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-02-ai-onboarding-prompt/slice.json +1 -1
  24. package/specs/quiver-v08-agent-onboarding-analysis/slices/slice-03-doctor-readme-adoption-flow/slice.json +1 -1
  25. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-01-cross-platform-support-contract/slice.json +1 -1
  26. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-02-node-init-docs-runtime/slice.json +1 -1
  27. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-03-node-migrate-analyze-doctor-flow/slice.json +1 -1
  28. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-04-node-slice-lifecycle-commands/slice.json +1 -1
  29. package/specs/quiver-v12-cross-platform-native-runtime/slices/slice-05-generated-project-scripts-and-migration/slice.json +1 -1
  30. package/specs/quiver-v13-token-efficient-ai-context/EVIDENCE_REPORT.md +1 -1
  31. package/specs/quiver-v13-token-efficient-ai-context/SPEC.md +1 -1
  32. package/specs/quiver-v15-init-required-before-migrate/EVIDENCE_REPORT.md +26 -0
  33. package/specs/quiver-v15-init-required-before-migrate/SPEC.md +66 -0
  34. package/specs/quiver-v15-init-required-before-migrate/STATUS.md +26 -0
  35. package/specs/quiver-v15-init-required-before-migrate/slices/slice-01-migrate-initialization-precondition/slice.json +65 -0
  36. package/specs/quiver-v15-init-required-before-migrate/slices/slice-02-doctor-not-initialized-guidance/slice.json +61 -0
  37. package/specs/quiver-v15-init-required-before-migrate/slices/slice-03-docs-smokes-init-before-migrate/slice.json +64 -0
  38. package/specs/quiver-v16-handoff-contract/EVIDENCE_REPORT.md +26 -0
  39. package/specs/quiver-v16-handoff-contract/SPEC.md +68 -0
  40. package/specs/quiver-v16-handoff-contract/STATUS.md +26 -0
  41. package/specs/quiver-v16-handoff-contract/slices/slice-01-handoff-template-and-contract/slice.json +66 -0
  42. package/specs/quiver-v16-handoff-contract/slices/slice-02-check-handoff-command/slice.json +70 -0
  43. package/specs/quiver-v16-handoff-contract/slices/slice-03-handoff-scaffold-optional/slice.json +67 -0
  44. package/specs/quiver-v17-orchestration-foundation/EVIDENCE_REPORT.md +32 -0
  45. package/specs/quiver-v17-orchestration-foundation/SPEC.md +79 -0
  46. package/specs/quiver-v17-orchestration-foundation/STATUS.md +31 -0
  47. package/specs/quiver-v17-orchestration-foundation/slices/slice-01-ci-matrix-verified/slice.json +68 -0
  48. package/specs/quiver-v17-orchestration-foundation/slices/slice-02-slice-graph-library/slice.json +65 -0
  49. package/specs/quiver-v17-orchestration-foundation/slices/slice-03-depends-on-validation/slice.json +72 -0
  50. package/specs/quiver-v18-slice-orchestration/EVIDENCE_REPORT.md +38 -0
  51. package/specs/quiver-v18-slice-orchestration/SPEC.md +91 -0
  52. package/specs/quiver-v18-slice-orchestration/STATUS.md +33 -0
  53. package/specs/quiver-v18-slice-orchestration/slices/slice-01-plan-command/slice.json +79 -0
  54. package/specs/quiver-v18-slice-orchestration/slices/slice-02-graph-mvp-tree/slice.json +75 -0
  55. package/specs/quiver-v18-slice-orchestration/slices/slice-03-graph-extended-formats/slice.json +70 -0
  56. package/specs/quiver-v18-slice-orchestration/slices/slice-04-next-command/slice.json +73 -0
  57. package/specs/quiver-v18-stabilization/EVIDENCE_REPORT.md +26 -0
  58. package/specs/quiver-v18-stabilization/SPEC.md +62 -0
  59. package/specs/quiver-v18-stabilization/STATUS.md +30 -0
  60. package/specs/quiver-v18-stabilization/slices/slice-01-fix-legacy-dependency-resolution/CLOSURE_BRIEF.md +29 -0
  61. package/specs/quiver-v18-stabilization/slices/slice-01-fix-legacy-dependency-resolution/EXECUTION_BRIEF.md +134 -0
  62. package/specs/quiver-v18-stabilization/slices/slice-01-fix-legacy-dependency-resolution/slice.json +56 -0
  63. package/specs/quiver-v18-stabilization/slices/slice-02-roadmap-and-branch-cleanup/CLOSURE_BRIEF.md +29 -0
  64. package/specs/quiver-v18-stabilization/slices/slice-02-roadmap-and-branch-cleanup/EXECUTION_BRIEF.md +118 -0
  65. package/specs/quiver-v18-stabilization/slices/slice-02-roadmap-and-branch-cleanup/slice.json +57 -0
  66. package/specs/quiver-v18-stabilization/slices/slice-03-publish-drafts-branch/CLOSURE_BRIEF.md +23 -0
  67. package/specs/quiver-v18-stabilization/slices/slice-03-publish-drafts-branch/EXECUTION_BRIEF.md +73 -0
  68. package/specs/quiver-v18-stabilization/slices/slice-03-publish-drafts-branch/slice.json +49 -0
  69. package/src/create-quiver/commands/graph.js +97 -0
  70. package/src/create-quiver/commands/next.js +134 -0
  71. package/src/create-quiver/commands/plan.js +205 -0
  72. package/src/create-quiver/index.js +179 -2
  73. package/src/create-quiver/lib/handoff.js +104 -0
  74. package/src/create-quiver/lib/init-docs.js +71 -13
  75. package/src/create-quiver/lib/json.js +14 -0
  76. package/src/create-quiver/lib/lifecycle.js +3 -2
  77. package/src/create-quiver/lib/readiness.js +55 -1
  78. package/src/create-quiver/lib/renderers/dot.js +129 -0
  79. package/src/create-quiver/lib/renderers/mermaid.js +119 -0
  80. package/src/create-quiver/lib/renderers/tree.js +116 -0
  81. package/src/create-quiver/lib/slice-graph.js +453 -0
  82. package/src/create-quiver/lib/slice.js +2 -1
  83. package/src/create-quiver/lib/state.js +50 -0
@@ -2,12 +2,17 @@ const fs = require('fs');
2
2
  const os = require('os');
3
3
  const path = require('path');
4
4
  const { execFileSync } = require('child_process');
5
+ const { checkHandoff, scaffoldHandoff } = require('./lib/handoff');
5
6
  const { collectDoctorWarnings } = require('./lib/doctor');
7
+ const { runGraph } = require('./commands/graph');
8
+ const { runNext } = require('./commands/next');
9
+ const { runPlan } = require('./commands/plan');
6
10
  const { initializeProjectDocs } = require('./lib/init-docs');
7
11
  const { checkPrReadiness, checkScope, checkSliceReadiness } = require('./lib/readiness');
8
12
  const { cleanupSlice, refreshActiveSlicesBoard, startSlice } = require('./lib/lifecycle');
9
13
  const { relativePosixPath, resolveTargetRoot } = require('./lib/paths');
10
14
  const {
15
+ hasQuiverInitializationEvidence,
11
16
  readState,
12
17
  updateStateForAnalyze,
13
18
  updateStateForMigrate,
@@ -23,11 +28,16 @@ function printUsage() {
23
28
  console.log(`Usage:
24
29
  npx create-quiver [options]
25
30
  npx create-quiver analyze [options]
31
+ npx create-quiver plan [options]
32
+ npx create-quiver graph [options]
33
+ npx create-quiver next [options]
26
34
  npx create-quiver migrate [options]
27
35
  npx create-quiver doctor [options]
28
36
  npx create-quiver start-slice [options] <slice.json>
29
37
  npx create-quiver check-slice [options] <slice.json>
30
38
  npx create-quiver check-pr <slice.json>
39
+ npx create-quiver check-handoff <handoff.md>
40
+ npx create-quiver new-handoff <spec-slug>
31
41
  npx create-quiver cleanup-slice [options] <slice.json>
32
42
  npx create-quiver check-scope [options] <slice.json>
33
43
  npx create-quiver refresh-active-slices
@@ -35,6 +45,15 @@ function printUsage() {
35
45
  Options:
36
46
  -n, --name <project-name> Project name to generate
37
47
  -d, --dir <target-dir> Target directory to scaffold into or inspect
48
+ --spec <slug> Restrict plan output to one spec
49
+ --format <name> Graph output format (tree, mermaid, dot)
50
+ --show-conflicts Show shared file paths in graph output
51
+ --level <n> Restrict graph output to one level
52
+ --json Emit machine-readable JSON
53
+ --only-ready Show only slices with no pending dependencies
54
+ --all-ready List every ready slice returned by next
55
+ --auto-start Prompt for confirmation and run start-slice on next
56
+ --unicode Prefer Unicode output when supported
38
57
  -y, --yes Skip prompts and use the provided inputs
39
58
  -h, --help Show this help message
40
59
 
@@ -42,11 +61,20 @@ Examples:
42
61
  npx create-quiver --name "My Project"
43
62
  npx create-quiver --name "My Project" --dir ./my-project
44
63
  cd ./my-project && npx create-quiver analyze
64
+ cd ./my-project && npx create-quiver plan --json
65
+ cd ./my-project && npx create-quiver graph --show-conflicts
66
+ cd ./my-project && npx create-quiver graph --format mermaid
67
+ cd ./my-project && npx create-quiver graph --format dot
68
+ cd ./my-project && npx create-quiver next
69
+ cd ./my-project && npx create-quiver next --all-ready
70
+ cd ./my-project && npx create-quiver next --auto-start
45
71
  cd ./my-project && npx create-quiver migrate
46
72
  cd ./my-project && npx create-quiver doctor
47
73
  cd ./my-project && npx create-quiver start-slice specs/my-project/slices/slice-01/slice.json
48
74
  cd ./my-project && npx create-quiver check-slice specs/my-project/slices/slice-01/slice.json
49
75
  cd ./my-project && npx create-quiver check-pr specs/my-project/slices/slice-01/slice.json
76
+ cd ./my-project && npx create-quiver check-handoff specs/my-project/HANDOFF.md
77
+ cd ./my-project && npx create-quiver new-handoff my-spec
50
78
  cd ./my-project && npx create-quiver cleanup-slice specs/my-project/slices/slice-01/slice.json
51
79
  cd ./my-project && npx create-quiver check-scope specs/my-project/slices/slice-01/slice.json
52
80
  cd ./my-project && npx create-quiver refresh-active-slices
@@ -68,10 +96,19 @@ function parseArgs(argv) {
68
96
  targetDir: '.',
69
97
  strict: false,
70
98
  strictOverlap: false,
99
+ json: false,
100
+ onlyReady: false,
101
+ allReady: false,
102
+ autoStart: false,
103
+ specSlug: '',
104
+ format: 'tree',
105
+ showConflicts: false,
106
+ level: null,
107
+ unicode: false,
71
108
  };
72
109
 
73
110
  const args = [...argv];
74
- const commandModes = new Set(['doctor', 'analyze', 'migrate', 'start-slice', 'check-slice', 'check-pr', 'cleanup-slice', 'check-scope', 'refresh-active-slices']);
111
+ const commandModes = new Set(['plan', 'graph', 'next', 'doctor', 'analyze', 'migrate', 'start-slice', 'check-slice', 'check-pr', 'check-handoff', 'new-handoff', 'cleanup-slice', 'check-scope', 'refresh-active-slices']);
75
112
  if (commandModes.has(args[0])) {
76
113
  result.mode = args[0];
77
114
  args.shift();
@@ -84,6 +121,12 @@ function parseArgs(argv) {
84
121
  } else if (args[0] === '--doctor') {
85
122
  result.mode = 'doctor';
86
123
  args.shift();
124
+ } else if (args[0] === '--check-handoff') {
125
+ result.mode = 'check-handoff';
126
+ args.shift();
127
+ } else if (args[0] === '--new-handoff') {
128
+ result.mode = 'new-handoff';
129
+ args.shift();
87
130
  }
88
131
 
89
132
  const positional = [];
@@ -111,6 +154,16 @@ function parseArgs(argv) {
111
154
  continue;
112
155
  }
113
156
 
157
+ if (arg === '--check-handoff') {
158
+ result.mode = 'check-handoff';
159
+ continue;
160
+ }
161
+
162
+ if (arg === '--new-handoff') {
163
+ result.mode = 'new-handoff';
164
+ continue;
165
+ }
166
+
114
167
  if (arg === '--allow-draft') {
115
168
  result.allowDraft = true;
116
169
  continue;
@@ -141,6 +194,67 @@ function parseArgs(argv) {
141
194
  continue;
142
195
  }
143
196
 
197
+ if (arg === '--json') {
198
+ result.json = true;
199
+ continue;
200
+ }
201
+
202
+ if (arg === '--show-conflicts') {
203
+ result.showConflicts = true;
204
+ continue;
205
+ }
206
+
207
+ if (arg === '--format') {
208
+ const value = args[++index];
209
+ if (!value) {
210
+ throw new Error(formatError('missing value for --format'));
211
+ }
212
+ result.format = value;
213
+ continue;
214
+ }
215
+
216
+ if (arg === '--level') {
217
+ const value = args[++index];
218
+ if (typeof value === 'undefined') {
219
+ throw new Error(formatError('missing value for --level'));
220
+ }
221
+ const parsed = Number.parseInt(value, 10);
222
+ if (!Number.isInteger(parsed) || parsed < 0) {
223
+ throw new Error(formatError('invalid value for --level'));
224
+ }
225
+ result.level = parsed;
226
+ continue;
227
+ }
228
+
229
+ if (arg === '--only-ready') {
230
+ result.onlyReady = true;
231
+ continue;
232
+ }
233
+
234
+ if (arg === '--all-ready') {
235
+ result.allReady = true;
236
+ continue;
237
+ }
238
+
239
+ if (arg === '--auto-start') {
240
+ result.autoStart = true;
241
+ continue;
242
+ }
243
+
244
+ if (arg === '--unicode') {
245
+ result.unicode = true;
246
+ continue;
247
+ }
248
+
249
+ if (arg === '--spec') {
250
+ const value = args[++index];
251
+ if (!value) {
252
+ throw new Error(formatError('missing value for --spec'));
253
+ }
254
+ result.specSlug = value;
255
+ continue;
256
+ }
257
+
144
258
  if (arg === '--gate') {
145
259
  const value = args[++index];
146
260
  if (!value) {
@@ -183,6 +297,10 @@ function parseArgs(argv) {
183
297
  if (positional.length > 0) {
184
298
  result.targetDir = positional.shift();
185
299
  }
300
+ } else if (result.mode === 'plan') {
301
+ if (positional.length > 0) {
302
+ throw new Error(formatError('plan does not accept positional arguments; use --spec <slug>'));
303
+ }
186
304
  } else if (result.mode === 'refresh-active-slices') {
187
305
  if (positional.length > 0) {
188
306
  throw new Error(formatError('refresh-active-slices does not accept positional arguments'));
@@ -1022,6 +1140,10 @@ function runMigrate(targetDir) {
1022
1140
  throw new Error(formatError(`target directory does not exist: ${projectRoot}`));
1023
1141
  }
1024
1142
 
1143
+ if (!hasQuiverInitializationEvidence(projectRoot)) {
1144
+ throw new Error(formatError('migrate requires a project previously initialized by Quiver.\nRun: npx create-quiver --name "Project Name"'));
1145
+ }
1146
+
1025
1147
  const packageJson = loadPackageJson(projectRoot);
1026
1148
  const projectName = packageJson.name || path.basename(projectRoot) || 'Quiver Project';
1027
1149
  const packageRoot = path.resolve(__dirname, '../..');
@@ -1052,6 +1174,10 @@ function runDoctor(targetDir) {
1052
1174
  throw new Error(formatError(`target directory does not exist: ${projectRoot}`));
1053
1175
  }
1054
1176
 
1177
+ if (!hasQuiverInitializationEvidence(projectRoot)) {
1178
+ throw new Error(formatError('doctor requires a project previously initialized by Quiver.\nRun init first: npx create-quiver --name "Project Name"'));
1179
+ }
1180
+
1055
1181
  const generatedSpecs = listGeneratedSpecDirs(projectRoot);
1056
1182
  if (generatedSpecs.length !== 1) {
1057
1183
  throw new Error(formatError(`expected exactly one generated spec directory, found ${generatedSpecs.length || 0}`));
@@ -1144,10 +1270,11 @@ function runDoctor(targetDir) {
1144
1270
  if (!hasQuiverState) {
1145
1271
  console.log('- Run migration first: npx create-quiver migrate');
1146
1272
  } else if (!hasScanArtifacts) {
1147
- console.log('- Analyze the project first: npx create-quiver analyze');
1273
+ console.log('- Analyze the project first: npx create-quiver analyze');
1148
1274
  } else {
1149
1275
  console.log('- Ask your AI agent: Read AGENTS.md, then docs/AI_ONBOARDING_PROMPT.md and execute it.');
1150
1276
  }
1277
+ console.log('- Check the next ready slice: npx create-quiver next');
1151
1278
  console.log(`- Start a slice: npx create-quiver start-slice specs/${projectSlug}/slices/slice-template/slice.json`);
1152
1279
  console.log(`- Validate a slice: npx create-quiver check-slice specs/${projectSlug}/slices/slice-template/slice.json`);
1153
1280
  console.log(`- Validate the PR gate: npx create-quiver check-pr specs/${projectSlug}/slices/slice-template/slice.json`);
@@ -1177,6 +1304,37 @@ async function run(argv) {
1177
1304
  return;
1178
1305
  }
1179
1306
 
1307
+ if (args.mode === 'plan') {
1308
+ runPlan(process.cwd(), {
1309
+ json: args.json,
1310
+ onlyReady: args.onlyReady,
1311
+ specSlug: args.specSlug,
1312
+ unicode: args.unicode,
1313
+ });
1314
+ return;
1315
+ }
1316
+
1317
+ if (args.mode === 'graph') {
1318
+ runGraph(process.cwd(), {
1319
+ format: args.format,
1320
+ json: args.json,
1321
+ level: args.level,
1322
+ showConflicts: args.showConflicts,
1323
+ unicode: args.unicode,
1324
+ });
1325
+ return;
1326
+ }
1327
+
1328
+ if (args.mode === 'next') {
1329
+ await runNext(process.cwd(), {
1330
+ allReady: args.allReady,
1331
+ autoStart: args.autoStart,
1332
+ json: args.json,
1333
+ specSlug: args.specSlug,
1334
+ });
1335
+ return;
1336
+ }
1337
+
1180
1338
  if (args.mode === 'migrate') {
1181
1339
  runMigrate(args.targetDir);
1182
1340
  return;
@@ -1205,6 +1363,25 @@ async function run(argv) {
1205
1363
  return;
1206
1364
  }
1207
1365
 
1366
+ if (args.mode === 'check-handoff') {
1367
+ const repoRoot = process.cwd();
1368
+ const handoffInput = args.targetDir;
1369
+ if (!handoffInput || handoffInput === '.') {
1370
+ throw new Error(formatError('missing handoff path. Use: npx create-quiver check-handoff specs/<spec-slug>/HANDOFF.md'));
1371
+ }
1372
+ const resolved = checkHandoff(handoffInput, repoRoot);
1373
+ console.log(`PASS: Handoff validated at ${resolved.relativePath}`);
1374
+ return;
1375
+ }
1376
+
1377
+ if (args.mode === 'new-handoff') {
1378
+ const repoRoot = process.cwd();
1379
+ const handoffSlug = args.targetDir;
1380
+ const resolved = scaffoldHandoff(handoffSlug, repoRoot);
1381
+ console.log(`PASS: Handoff scaffolded at ${resolved.relativePath}`);
1382
+ return;
1383
+ }
1384
+
1208
1385
  if (args.mode === 'cleanup-slice') {
1209
1386
  cleanupSlice(path.resolve(process.cwd(), args.targetDir), {
1210
1387
  closeBaseline: args.closeBaseline,
@@ -0,0 +1,104 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ const REQUIRED_HEADINGS = [
5
+ '## Background',
6
+ '## What you will change',
7
+ '## Validation checklist',
8
+ '## Out of scope',
9
+ '## Expected deliverable',
10
+ '## Constraints',
11
+ ];
12
+
13
+ const HANDOFF_TEMPLATE_PATH = path.resolve(__dirname, '..', '..', '..', 'specs', '[project-name]', 'HANDOFF.md.template');
14
+
15
+ function normalizePosixPath(filePath, pathLib = path) {
16
+ return filePath.split(pathLib.sep).join('/');
17
+ }
18
+
19
+ function resolveHandoffPath(repoRoot, handoffInput, pathLib = path) {
20
+ const absolutePath = pathLib.resolve(repoRoot, handoffInput);
21
+ const relativePath = normalizePosixPath(pathLib.relative(repoRoot, absolutePath), pathLib);
22
+
23
+ if (relativePath.startsWith('..') || pathLib.isAbsolute(relativePath)) {
24
+ throw new Error(`create-quiver: handoff must live at specs/<spec-slug>/HANDOFF.md (got ${normalizePosixPath(handoffInput, pathLib)})`);
25
+ }
26
+
27
+ const match = relativePath.match(/^specs\/([^/]+)\/HANDOFF\.md$/);
28
+ if (!match) {
29
+ throw new Error(`create-quiver: handoff must live at specs/<spec-slug>/HANDOFF.md (got ${relativePath})`);
30
+ }
31
+
32
+ return {
33
+ absolutePath,
34
+ relativePath,
35
+ specSlug: match[1],
36
+ };
37
+ }
38
+
39
+ function readHandoffSections(text) {
40
+ return String(text || '')
41
+ .split(/\r?\n/)
42
+ .map((line) => line.trim())
43
+ .filter((line) => line.startsWith('## '))
44
+ .map((line) => line.replace(/\s+$/, ''));
45
+ }
46
+
47
+ function validateHandoffSections(text) {
48
+ const sections = new Set(readHandoffSections(text));
49
+ return REQUIRED_HEADINGS.filter((heading) => !sections.has(heading));
50
+ }
51
+
52
+ function checkHandoff(handoffInput, repoRoot = process.cwd()) {
53
+ const resolved = resolveHandoffPath(repoRoot, handoffInput);
54
+
55
+ if (!fs.existsSync(resolved.absolutePath)) {
56
+ throw new Error(`create-quiver: missing handoff file: ${resolved.relativePath}`);
57
+ }
58
+
59
+ const text = fs.readFileSync(resolved.absolutePath, 'utf8');
60
+ const missingSections = validateHandoffSections(text);
61
+ if (missingSections.length > 0) {
62
+ throw new Error(`create-quiver: handoff is missing required sections: ${missingSections.join(', ')}`);
63
+ }
64
+
65
+ return resolved;
66
+ }
67
+
68
+ function scaffoldHandoff(specSlug, repoRoot = process.cwd()) {
69
+ const trimmedSlug = String(specSlug || '').trim();
70
+ if (!trimmedSlug) {
71
+ throw new Error('create-quiver: missing handoff slug. Use: npx create-quiver new-handoff <spec-slug>');
72
+ }
73
+
74
+ if (!fs.existsSync(HANDOFF_TEMPLATE_PATH)) {
75
+ throw new Error('create-quiver: missing handoff template at specs/[project-name]/HANDOFF.md.template');
76
+ }
77
+
78
+ const resolved = resolveHandoffPath(repoRoot, path.join('specs', trimmedSlug, 'HANDOFF.md'));
79
+ if (fs.existsSync(resolved.absolutePath)) {
80
+ throw new Error(`create-quiver: handoff already exists at ${resolved.relativePath}`);
81
+ }
82
+
83
+ const templateText = fs.readFileSync(HANDOFF_TEMPLATE_PATH, 'utf8');
84
+ const projectName = trimmedSlug.replace(/-/g, ' ');
85
+ const currentDate = new Date().toISOString().slice(0, 10);
86
+ const renderedText = templateText
87
+ .replace(/{{PROJECT_NAME}}/g, projectName)
88
+ .replace(/{{PROJECT_SLUG}}/g, trimmedSlug)
89
+ .replace(/{{FECHA}}/g, currentDate);
90
+
91
+ fs.mkdirSync(path.dirname(resolved.absolutePath), { recursive: true });
92
+ fs.writeFileSync(resolved.absolutePath, renderedText);
93
+
94
+ return checkHandoff(resolved.relativePath, repoRoot);
95
+ }
96
+
97
+ module.exports = {
98
+ REQUIRED_HEADINGS,
99
+ checkHandoff,
100
+ readHandoffSections,
101
+ scaffoldHandoff,
102
+ resolveHandoffPath,
103
+ validateHandoffSections,
104
+ };
@@ -61,6 +61,9 @@ function renderTemplate(text, replacements) {
61
61
  .replace(/{{PRIMARY_DEV}}/g, replacements.primaryDev || 'not defined')
62
62
  .replace(/{{PRIMARY_TEST}}/g, replacements.primaryTest || 'not defined')
63
63
  .replace(/{{ANALYZE_COMMAND}}/g, replacements.analyzeCommand || 'npx create-quiver analyze')
64
+ .replace(/{{PLAN_COMMAND}}/g, replacements.planCommand || 'npx create-quiver plan')
65
+ .replace(/{{GRAPH_COMMAND}}/g, replacements.graphCommand || 'npx create-quiver graph')
66
+ .replace(/{{NEXT_COMMAND}}/g, replacements.nextCommand || 'npx create-quiver next')
64
67
  .replace(/{{DOCTOR_COMMAND}}/g, replacements.doctorCommand || 'npx create-quiver doctor')
65
68
  .replace(/{{START_SLICE_COMMAND}}/g, replacements.startSliceCommand || 'npx create-quiver start-slice <slice.json>')
66
69
  .replace(/{{CHECK_SLICE_COMMAND}}/g, replacements.checkSliceCommand || 'npx create-quiver check-slice <slice.json>')
@@ -227,12 +230,22 @@ function buildReadme(projectName, projectSlug) {
227
230
 
228
231
  ## Quick Start
229
232
 
230
- Run Quiver from this project root. Do not install it globally.
233
+ Run Quiver from this project root. Do not install it globally.
231
234
 
232
235
  \`\`\`bash
233
236
  npm install
234
- npx create-quiver analyze
235
- npx create-quiver doctor
237
+ {{ANALYZE_COMMAND}}
238
+ {{PLAN_COMMAND}}
239
+ {{GRAPH_COMMAND}}
240
+ {{DOCTOR_COMMAND}}
241
+ {{NEXT_COMMAND}}
242
+ \`\`\`
243
+
244
+ Exportable graph formats are available when you need a PR-ready Mermaid block or Graphviz source:
245
+
246
+ \`\`\`bash
247
+ {{GRAPH_COMMAND}} --format mermaid
248
+ {{GRAPH_COMMAND}} --format dot
236
249
  \`\`\`
237
250
 
238
251
  If this project needs a pinned Quiver version, install it as a devDependency:
@@ -251,17 +264,28 @@ The generated project includes \`quiver:*\` npm scripts that call the Node CLI a
251
264
 
252
265
  \`\`\`bash
253
266
  npm run quiver:analyze
267
+ npm run quiver:plan
268
+ npm run quiver:graph
269
+ npm run quiver:next
254
270
  npm run quiver:doctor
255
271
  npm run quiver:migrate
256
272
  npm run quiver:start-slice -- specs/${projectSlug}/slices/slice-01/slice.json
257
273
  npm run quiver:check-slice -- specs/${projectSlug}/slices/slice-01/slice.json
258
274
  npm run quiver:check-pr -- specs/${projectSlug}/slices/slice-01/slice.json
275
+ npm run quiver:check-handoff -- specs/${projectSlug}/HANDOFF.md
259
276
  npm run quiver:cleanup-slice -- specs/${projectSlug}/slices/slice-01/slice.json
260
277
  npm run quiver:check-scope -- specs/${projectSlug}/slices/slice-01/slice.json
261
278
  npm run quiver:refresh-active-slices
262
279
  \`\`\`
263
280
 
281
+ The \`quiver:graph\` script prints the tree view by default; use \`npx create-quiver graph --format mermaid\` for PR-ready Markdown and \`--format dot\` when you want Graphviz source.
282
+ The \`quiver:next\` script points to the next ready slice and can auto-start it behind a confirmation prompt.
283
+ Use \`npx create-quiver next --all-ready\` when you want the full ready level instead of a single suggestion.
264
284
  The legacy Bash wrappers remain in \`tools/scripts/\` for compatibility, but new project-level automation should prefer the \`quiver:*\` scripts and the direct \`npx create-quiver ...\` commands below.
285
+ \`npm run quiver:migrate\` is only for projects that were already initialized by Quiver.
286
+ \`npm run check-handoff -- specs/${projectSlug}/HANDOFF.md\` is available as a legacy-friendly alias for the handoff validator.
287
+ If a new bounded transfer is needed, scaffold \`specs/${projectSlug}/HANDOFF.md\` with \`npx create-quiver new-handoff ${projectSlug}\` and validate it with \`npx create-quiver check-handoff specs/${projectSlug}/HANDOFF.md\`.
288
+ For exceptional context transfers between agents or phases, a dedicated \`HANDOFF.md\` can live alongside the usual spec and docs files.
265
289
 
266
290
  ## Cross-Platform Support
267
291
 
@@ -274,8 +298,19 @@ If the project already existed before this Quiver version, upgrade it from the p
274
298
  \`\`\`bash
275
299
  cd /path/to/your-project
276
300
  npx create-quiver migrate
277
- npx create-quiver analyze
278
- npx create-quiver doctor
301
+ {{ANALYZE_COMMAND}}
302
+ {{PLAN_COMMAND}}
303
+ {{GRAPH_COMMAND}}
304
+ {{NEXT_COMMAND}}
305
+ {{DOCTOR_COMMAND}}
306
+ \`\`\`
307
+
308
+ Use \`{{GRAPH_COMMAND}} --format mermaid\` for GitHub-friendly graph embeds or \`{{GRAPH_COMMAND}} --format dot\` for Graphviz pipelines.
309
+
310
+ If the project never ran Quiver initialization before, do not use \`migrate\` as bootstrap. Run:
311
+
312
+ \`\`\`bash
313
+ npx create-quiver --name "Project Name"
279
314
  \`\`\`
280
315
 
281
316
  If your team prefers a pinned local dependency, update the package first and then run the same flow:
@@ -283,10 +318,15 @@ If your team prefers a pinned local dependency, update the package first and the
283
318
  \`\`\`bash
284
319
  npm install --save-dev create-quiver@latest
285
320
  npx create-quiver migrate
286
- npx create-quiver analyze
287
- npx create-quiver doctor
321
+ {{ANALYZE_COMMAND}}
322
+ {{PLAN_COMMAND}}
323
+ {{GRAPH_COMMAND}}
324
+ {{NEXT_COMMAND}}
325
+ {{DOCTOR_COMMAND}}
288
326
  \`\`\`
289
327
 
328
+ The tree output remains the default, but Mermaid and DOT are available on demand for exported docs and slide decks.
329
+
290
330
  ## AI Context Onboarding
291
331
 
292
332
  Read \`AGENTS.md\` first, then open \`docs/AI_ONBOARDING_PROMPT.md\` after analysis.
@@ -300,6 +340,7 @@ Prepare the project context docs and report assumptions, risks, and files change
300
340
  \`\`\`
301
341
 
302
342
  Review the AI changes to docs/AI_CONTEXT.md, docs/CONTEXTO.md, docs/STATUS.md, and specs/${projectSlug}/SPEC.md before starting implementation work. Use \`docs/PROJECT_MAP.md\` for stack and command details.
343
+ If the work was explicitly transferred through a handoff artifact, read \`specs/${projectSlug}/HANDOFF.md\` before implementation.
303
344
 
304
345
  ## Decision Log
305
346
 
@@ -309,15 +350,21 @@ Record durable decisions in \`docs/DECISIONS.md\` so future AI agents do not re-
309
350
 
310
351
  1. Review or refine specs/${projectSlug}/SPEC.md.
311
352
  2. Create the first slice from specs/${projectSlug}/slices/slice-template/slice.json.
312
- 3. Start work with \`npx create-quiver start-slice <slice.json>\` or \`npm run quiver:start-slice -- <slice.json>\`.
313
- 4. Make one commit per slice.
314
- 5. Open one PR per spec.
353
+ 3. Review the plan with \`{{PLAN_COMMAND}}\` or \`npm run quiver:plan\`.
354
+ 4. Inspect parallel lots with \`{{GRAPH_COMMAND}}\` or \`npm run quiver:graph\`.
355
+ 5. Check the next ready slice with \`{{NEXT_COMMAND}}\` or \`npm run quiver:next\`.
356
+ 6. Start work with \`{{START_SLICE_COMMAND}}\` or \`npm run quiver:start-slice -- <slice.json>\`.
357
+ 7. Make one commit per slice.
358
+ 8. Open one PR per spec.
315
359
 
316
360
  ## Verification Checklist
317
361
 
318
362
  - [ ] npm install completes
319
- - [ ] npx create-quiver analyze completes
320
- - [ ] npx create-quiver doctor completes
363
+ - [ ] {{ANALYZE_COMMAND}} completes
364
+ - [ ] {{PLAN_COMMAND}} completes
365
+ - [ ] {{GRAPH_COMMAND}} completes
366
+ - [ ] {{NEXT_COMMAND}} completes
367
+ - [ ] {{DOCTOR_COMMAND}} completes
321
368
  - [ ] AI agent executed docs/AI_ONBOARDING_PROMPT.md
322
369
  - [ ] Context docs were reviewed before the first slice
323
370
 
@@ -326,6 +373,9 @@ Record durable decisions in \`docs/DECISIONS.md\` so future AI agents do not re-
326
373
  - [AI Context](./docs/AI_CONTEXT.md) - Contexto resumido para IA
327
374
  - [Decision Log](./docs/DECISIONS.md) - Decisiones durables del proyecto
328
375
  - [AI Onboarding Prompt](./docs/AI_ONBOARDING_PROMPT.md) - Handoff exacto para agentes después del análisis
376
+ - [Handoff](./specs/${projectSlug}/HANDOFF.md) - Transferencia excepcional entre agentes o fases
377
+ - [Check Handoff](./docs/WORKFLOW.md) - Valida el handoff con \`npx create-quiver check-handoff\`
378
+ - [Commands](./docs/COMMANDS.md) - Tabla canónica de comandos de orquestación
329
379
  - [Contexto](./docs/CONTEXTO.md) - Qué es ${projectName}
330
380
  - [Workflow](./docs/WORKFLOW.md) - Cómo implementar
331
381
  - [Support Matrix](./docs/SUPPORT_MATRIX.md) - Qué entornos están soportados
@@ -382,6 +432,7 @@ function initializeProjectDocs(options) {
382
432
  });
383
433
  const templateCopies = [
384
434
  ['docs/INDEX.md.template', 'docs/INDEX.md'],
435
+ ['docs/COMMANDS.md.template', 'docs/COMMANDS.md'],
385
436
  ['docs/DECISIONS.md.template', 'docs/DECISIONS.md'],
386
437
  ['docs/AI_CONTEXT.md.template', 'docs/AI_CONTEXT.md', frontMatterFor('Agent-facing project context pack', 'onboarding, implementation, review')],
387
438
  ['docs/AI_ONBOARDING_PROMPT.md.template', 'docs/AI_ONBOARDING_PROMPT.md', frontMatterFor('AI onboarding handoff prompt', 'onboarding after analysis')],
@@ -398,6 +449,7 @@ function initializeProjectDocs(options) {
398
449
  ['docs/TESTING_GUIDE_FOR_AI.md.template', 'docs/TESTING_GUIDE_FOR_AI.md'],
399
450
  ['docs/ai/LESSONS.md.template', 'docs/ai/LESSONS.md', frontMatterFor('Slice learnings log', 'after slice completion')],
400
451
  ['specs/[project-name]/SPEC.md.template', `specs/${replacements.projectSlug}/SPEC.md`],
452
+ ['specs/[project-name]/HANDOFF.md.template', `specs/${replacements.projectSlug}/HANDOFF.md`],
401
453
  ['specs/[project-name]/STATUS.md.template', `specs/${replacements.projectSlug}/STATUS.md`],
402
454
  ['specs/[project-name]/EVIDENCE_REPORT.md.template', `specs/${replacements.projectSlug}/EVIDENCE_REPORT.md`],
403
455
  ['specs/[project-name]/slices/slice-template/slice.json', `specs/${replacements.projectSlug}/slices/slice-template/slice.json`],
@@ -479,6 +531,9 @@ function initializeProjectDocs(options) {
479
531
  primaryDev: packageScripts.dev || packageScripts.start || 'not defined',
480
532
  primaryTest: packageScripts.test || 'not defined',
481
533
  analyzeCommand: 'npx create-quiver analyze',
534
+ planCommand: 'npx create-quiver plan',
535
+ graphCommand: 'npx create-quiver graph',
536
+ nextCommand: 'npx create-quiver next',
482
537
  doctorCommand: 'npx create-quiver doctor',
483
538
  startSliceCommand: 'npx create-quiver start-slice <slice.json>',
484
539
  checkSliceCommand: 'npx create-quiver check-slice <slice.json>',
@@ -492,6 +547,9 @@ function initializeProjectDocs(options) {
492
547
  ['docs/QUICK.md.template', 'docs/ai/QUICK.md'],
493
548
  ['docs/STANDARD.md.template', 'docs/ai/STANDARD.md'],
494
549
  ['docs/DEEP.md.template', 'docs/ai/DEEP.md'],
550
+ ['docs/examples/plan.md.template', 'docs/examples/plan.md'],
551
+ ['docs/examples/graph.md.template', 'docs/examples/graph.md'],
552
+ ['docs/examples/next.md.template', 'docs/examples/next.md'],
495
553
  ];
496
554
 
497
555
  for (const [source, destination] of tierCopies) {
@@ -597,7 +655,7 @@ function initializeProjectDocs(options) {
597
655
 
598
656
  const readmePath = path.join(projectRoot, 'README.md');
599
657
  if (!fs.existsSync(readmePath)) {
600
- fs.writeFileSync(readmePath, `${buildReadme(projectName, replacements.projectSlug)}\n`);
658
+ fs.writeFileSync(readmePath, `${renderTemplate(buildReadme(projectName, replacements.projectSlug), replacements)}\n`);
601
659
  operations.push({ source: 'README.md template', destination: 'README.md', result: 'created' });
602
660
  } else {
603
661
  operations.push({ source: 'README.md template', destination: 'README.md', result: 'skipped' });
@@ -0,0 +1,14 @@
1
+ function stripJsonComments(text) {
2
+ return String(text || '')
3
+ .replace(/^\s*\/\/.*$/gm, '')
4
+ .replace(/\/\*[\s\S]*?\*\//g, '');
5
+ }
6
+
7
+ function parseJsonWithComments(text) {
8
+ return JSON.parse(stripJsonComments(text));
9
+ }
10
+
11
+ module.exports = {
12
+ parseJsonWithComments,
13
+ stripJsonComments,
14
+ };
@@ -1,6 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { branchDelete, catFileExists, currentBranch, fetchBranch, fetchRemote, hasLocalBranch, hasRemoteBranch, lsRemoteHeads, mergeBaseIsAncestor, revListCount, runGit, statusPorcelain, worktreeAdd, worktreeList, worktreePrune, worktreeRemove } = require('./git');
4
+ const { parseJsonWithComments } = require('./json');
4
5
  const { writeFrontMatter } = require('./init-docs');
5
6
  const { resolveTargetRoot } = require('./paths');
6
7
  const { activeSlicePath, renderActiveSlice, resolveSliceContext, safeBranchName, toAlias, validateSliceMetaForStart, worktreesRootForRepo } = require('./slice');
@@ -131,7 +132,7 @@ function refreshActiveSlicesBoard(repoRoot) {
131
132
  continue;
132
133
  }
133
134
  if (entry.isFile() && entry.name === 'slice.json' && fullPath.includes(`${path.sep}slices${path.sep}`)) {
134
- const json = JSON.parse(fs.readFileSync(fullPath, 'utf8'));
135
+ const json = parseJsonWithComments(fs.readFileSync(fullPath, 'utf8'));
135
136
  const relPath = path.relative(repoRoot, fullPath);
136
137
  const parts = relPath.split(path.sep);
137
138
  const specFamily = parts[0];
@@ -181,7 +182,7 @@ function refreshActiveSlicesBoard(repoRoot) {
181
182
  const liveSlicePath = path.join(worktreePath, slice.relPath);
182
183
  if (fs.existsSync(liveSlicePath)) {
183
184
  try {
184
- const liveJson = JSON.parse(fs.readFileSync(liveSlicePath, 'utf8'));
185
+ const liveJson = parseJsonWithComments(fs.readFileSync(liveSlicePath, 'utf8'));
185
186
  liveStatus = liveJson.status || liveStatus;
186
187
  } catch {
187
188
  // ignore