create-quiver 0.10.0 → 0.12.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 (165) hide show
  1. package/BACKLOG.md +16 -17
  2. package/CHANGELOG.md +34 -0
  3. package/README.md +174 -39
  4. package/README_FOR_AI.md +48 -24
  5. package/ROADMAP.md +22 -11
  6. package/docs/AI_CONTEXT.md.template +2 -0
  7. package/docs/AI_ONBOARDING_PROMPT.md.template +25 -18
  8. package/docs/COMMANDS.md.template +59 -11
  9. package/docs/CONTEXTO.md.template +2 -0
  10. package/docs/DECISIONS.md.template +1 -0
  11. package/docs/INDEX.md.template +20 -18
  12. package/docs/STATUS.md.template +1 -0
  13. package/docs/SUPPORT_MATRIX.md.template +2 -2
  14. package/docs/TROUBLESHOOTING.md.template +50 -0
  15. package/docs/WORKFLOW.md.template +25 -17
  16. package/package.json +19 -2
  17. package/package.template.json +13 -1
  18. package/scripts/init-docs.sh +11 -4
  19. package/scripts/package-quiver.sh +18 -2
  20. package/specs/quiver-v22-guided-ai-workflow/EVIDENCE_REPORT.md +58 -0
  21. package/specs/quiver-v22-guided-ai-workflow/EXECUTION_PLAN.md +88 -0
  22. package/specs/quiver-v22-guided-ai-workflow/SPEC.md +228 -0
  23. package/specs/quiver-v22-guided-ai-workflow/STATUS.md +42 -0
  24. package/specs/quiver-v22-guided-ai-workflow/pr.md +104 -0
  25. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +35 -0
  26. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
  27. package/specs/quiver-v22-guided-ai-workflow/slices/slice-00-spec-foundation/slice.json +51 -0
  28. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/CLOSURE_BRIEF.md +31 -0
  29. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/EXECUTION_BRIEF.md +58 -0
  30. package/specs/quiver-v22-guided-ai-workflow/slices/slice-01-docs-source-of-truth-sync/slice.json +55 -0
  31. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/CLOSURE_BRIEF.md +30 -0
  32. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/EXECUTION_BRIEF.md +57 -0
  33. package/specs/quiver-v22-guided-ai-workflow/slices/slice-02-prepare-command-diagnostics/slice.json +57 -0
  34. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/CLOSURE_BRIEF.md +32 -0
  35. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/EXECUTION_BRIEF.md +56 -0
  36. package/specs/quiver-v22-guided-ai-workflow/slices/slice-03-context-doc-refresh/slice.json +56 -0
  37. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/CLOSURE_BRIEF.md +33 -0
  38. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/EXECUTION_BRIEF.md +56 -0
  39. package/specs/quiver-v22-guided-ai-workflow/slices/slice-04-planner-approval-state/slice.json +58 -0
  40. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/CLOSURE_BRIEF.md +32 -0
  41. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/EXECUTION_BRIEF.md +56 -0
  42. package/specs/quiver-v22-guided-ai-workflow/slices/slice-05-spec-worktree-lifecycle/slice.json +54 -0
  43. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/CLOSURE_BRIEF.md +32 -0
  44. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/EXECUTION_BRIEF.md +58 -0
  45. package/specs/quiver-v22-guided-ai-workflow/slices/slice-06-executor-commit-recovery/slice.json +57 -0
  46. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/CLOSURE_BRIEF.md +32 -0
  47. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/EXECUTION_BRIEF.md +58 -0
  48. package/specs/quiver-v22-guided-ai-workflow/slices/slice-07-execution-waves-delegation/slice.json +55 -0
  49. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/CLOSURE_BRIEF.md +32 -0
  50. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/EXECUTION_BRIEF.md +58 -0
  51. package/specs/quiver-v22-guided-ai-workflow/slices/slice-08-pr-create-gh-ssh/slice.json +53 -0
  52. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/CLOSURE_BRIEF.md +33 -0
  53. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/EXECUTION_BRIEF.md +59 -0
  54. package/specs/quiver-v22-guided-ai-workflow/slices/slice-09-post-merge-cleanup-release-safety/slice.json +59 -0
  55. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +34 -0
  56. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +58 -0
  57. package/specs/quiver-v22-guided-ai-workflow/slices/slice-10-docs-smokes-release-readiness/slice.json +60 -0
  58. package/specs/quiver-v23-guided-flow-productization/EVIDENCE_REPORT.md +80 -0
  59. package/specs/quiver-v23-guided-flow-productization/EXECUTION_PLAN.md +80 -0
  60. package/specs/quiver-v23-guided-flow-productization/SPEC.md +203 -0
  61. package/specs/quiver-v23-guided-flow-productization/STATUS.md +39 -0
  62. package/specs/quiver-v23-guided-flow-productization/pr.md +119 -0
  63. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +30 -0
  64. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +61 -0
  65. package/specs/quiver-v23-guided-flow-productization/slices/slice-00-spec-foundation/slice.json +51 -0
  66. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/CLOSURE_BRIEF.md +33 -0
  67. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/EXECUTION_BRIEF.md +35 -0
  68. package/specs/quiver-v23-guided-flow-productization/slices/slice-01-short-command-and-flow-entrypoint/slice.json +56 -0
  69. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/CLOSURE_BRIEF.md +31 -0
  70. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/EXECUTION_BRIEF.md +29 -0
  71. package/specs/quiver-v23-guided-flow-productization/slices/slice-02-flow-status-wizard/slice.json +55 -0
  72. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/CLOSURE_BRIEF.md +33 -0
  73. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/EXECUTION_BRIEF.md +29 -0
  74. package/specs/quiver-v23-guided-flow-productization/slices/slice-03-agent-profiles/slice.json +54 -0
  75. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/CLOSURE_BRIEF.md +32 -0
  76. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/EXECUTION_BRIEF.md +30 -0
  77. package/specs/quiver-v23-guided-flow-productization/slices/slice-04-context-preparation-onboarding/slice.json +59 -0
  78. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/CLOSURE_BRIEF.md +31 -0
  79. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/EXECUTION_BRIEF.md +29 -0
  80. package/specs/quiver-v23-guided-flow-productization/slices/slice-05-planner-iteration-history/slice.json +53 -0
  81. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/CLOSURE_BRIEF.md +33 -0
  82. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/EXECUTION_BRIEF.md +30 -0
  83. package/specs/quiver-v23-guided-flow-productization/slices/slice-06-production-plan-review/slice.json +54 -0
  84. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/CLOSURE_BRIEF.md +33 -0
  85. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/EXECUTION_BRIEF.md +30 -0
  86. package/specs/quiver-v23-guided-flow-productization/slices/slice-07-spec-create-experience/slice.json +55 -0
  87. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/CLOSURE_BRIEF.md +32 -0
  88. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/EXECUTION_BRIEF.md +30 -0
  89. package/specs/quiver-v23-guided-flow-productization/slices/slice-08-executor-prompt-generation/slice.json +55 -0
  90. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/CLOSURE_BRIEF.md +33 -0
  91. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/EXECUTION_BRIEF.md +34 -0
  92. package/specs/quiver-v23-guided-flow-productization/slices/slice-09-delegated-slice-execution/slice.json +57 -0
  93. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +33 -0
  94. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +32 -0
  95. package/specs/quiver-v23-guided-flow-productization/slices/slice-10-docs-smokes-release-readiness/slice.json +63 -0
  96. package/specs/quiver-v24-dx-onboarding-hardening/EVIDENCE_REPORT.md +55 -0
  97. package/specs/quiver-v24-dx-onboarding-hardening/EXECUTION_PLAN.md +43 -0
  98. package/specs/quiver-v24-dx-onboarding-hardening/SPEC.md +149 -0
  99. package/specs/quiver-v24-dx-onboarding-hardening/STATUS.md +31 -0
  100. package/specs/quiver-v24-dx-onboarding-hardening/pr.md +76 -0
  101. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +31 -0
  102. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +52 -0
  103. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-00-spec-foundation/slice.json +51 -0
  104. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/CLOSURE_BRIEF.md +38 -0
  105. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/EXECUTION_BRIEF.md +53 -0
  106. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-01-init-template-hygiene/slice.json +55 -0
  107. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/CLOSURE_BRIEF.md +33 -0
  108. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/EXECUTION_BRIEF.md +50 -0
  109. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-02-cli-command-routing-version-errors/slice.json +52 -0
  110. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/CLOSURE_BRIEF.md +33 -0
  111. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/EXECUTION_BRIEF.md +50 -0
  112. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-03-doctor-fix-doc-link-checks/slice.json +53 -0
  113. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/CLOSURE_BRIEF.md +33 -0
  114. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/EXECUTION_BRIEF.md +50 -0
  115. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-04-prepare-output-ai-context-drafts/slice.json +70 -0
  116. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/CLOSURE_BRIEF.md +36 -0
  117. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/EXECUTION_BRIEF.md +49 -0
  118. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-05-local-slice-validation-base-guidance/slice.json +52 -0
  119. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/CLOSURE_BRIEF.md +43 -0
  120. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/EXECUTION_BRIEF.md +53 -0
  121. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-06-plan-graph-next-history-views/slice.json +60 -0
  122. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/CLOSURE_BRIEF.md +32 -0
  123. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/EXECUTION_BRIEF.md +50 -0
  124. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-07-analyzer-command-map-hardening/slice.json +51 -0
  125. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/CLOSURE_BRIEF.md +34 -0
  126. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/EXECUTION_BRIEF.md +52 -0
  127. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-08-evidence-run-command/slice.json +54 -0
  128. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/CLOSURE_BRIEF.md +34 -0
  129. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/EXECUTION_BRIEF.md +51 -0
  130. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-09-spec-viewer-demo-scaffolding/slice.json +59 -0
  131. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/CLOSURE_BRIEF.md +33 -0
  132. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/EXECUTION_BRIEF.md +54 -0
  133. package/specs/quiver-v24-dx-onboarding-hardening/slices/slice-10-docs-smokes-release-readiness/slice.json +76 -0
  134. package/src/create-quiver/commands/ai.js +508 -35
  135. package/src/create-quiver/commands/demo.js +22 -0
  136. package/src/create-quiver/commands/evidence.js +37 -0
  137. package/src/create-quiver/commands/flow.js +561 -0
  138. package/src/create-quiver/commands/graph.js +14 -1
  139. package/src/create-quiver/commands/next.js +28 -0
  140. package/src/create-quiver/commands/plan.js +6 -3
  141. package/src/create-quiver/commands/prepare.js +236 -0
  142. package/src/create-quiver/commands/spec.js +133 -0
  143. package/src/create-quiver/index.js +688 -25
  144. package/src/create-quiver/lib/agent-profiles.js +148 -0
  145. package/src/create-quiver/lib/ai/context-packs.js +12 -0
  146. package/src/create-quiver/lib/ai/execution-plan.js +370 -10
  147. package/src/create-quiver/lib/ai/executor.js +376 -17
  148. package/src/create-quiver/lib/ai/github.js +196 -0
  149. package/src/create-quiver/lib/ai/onboarding-template.js +365 -0
  150. package/src/create-quiver/lib/ai/plan-review.js +283 -0
  151. package/src/create-quiver/lib/ai/providers.js +1 -0
  152. package/src/create-quiver/lib/ai/safety.js +5 -0
  153. package/src/create-quiver/lib/ai/spec-templates.js +2 -2
  154. package/src/create-quiver/lib/approvals.js +350 -0
  155. package/src/create-quiver/lib/demo.js +657 -0
  156. package/src/create-quiver/lib/doctor.js +234 -0
  157. package/src/create-quiver/lib/evidence.js +115 -0
  158. package/src/create-quiver/lib/init-docs.js +284 -17
  159. package/src/create-quiver/lib/init-layout.js +26 -1
  160. package/src/create-quiver/lib/lifecycle.js +6 -0
  161. package/src/create-quiver/lib/package-safety.js +117 -0
  162. package/src/create-quiver/lib/readiness.js +85 -18
  163. package/src/create-quiver/lib/slice-graph.js +1 -0
  164. package/src/create-quiver/lib/slice.js +8 -8
  165. package/src/create-quiver/lib/spec-worktrees.js +349 -0
@@ -3,6 +3,11 @@ const path = require('path');
3
3
  const { readAllSlices } = require('./slice-graph');
4
4
  const { hasGeneratedProjectSpec, hasInitializedStateMetadata, readState } = require('./state');
5
5
  const { worktreeList } = require('./git');
6
+ const {
7
+ buildQuiverConfig,
8
+ buildQuiverInternalGitignore,
9
+ resolveInitPackageScripts,
10
+ } = require('./init-layout');
6
11
 
7
12
  const NEW_LAYOUT_REQUIRED_PATHS = [
8
13
  'README.md',
@@ -26,6 +31,13 @@ const LEGACY_LAYOUT_PROBES = [
26
31
  'docs/PROJECT_SCAN.json',
27
32
  ];
28
33
 
34
+ const ROOT_GITIGNORE_DEFAULTS = [
35
+ 'node_modules/',
36
+ '.DS_Store',
37
+ 'dist/',
38
+ 'coverage/',
39
+ ];
40
+
29
41
  function readTextIfExists(filePath) {
30
42
  if (!fs.existsSync(filePath)) {
31
43
  return null;
@@ -34,6 +46,34 @@ function readTextIfExists(filePath) {
34
46
  return fs.readFileSync(filePath, 'utf8');
35
47
  }
36
48
 
49
+ function normalizeIgnorePattern(line) {
50
+ const trimmed = line.trim();
51
+ if (!trimmed || trimmed.startsWith('#')) {
52
+ return trimmed;
53
+ }
54
+
55
+ return trimmed.replace(/\/+$/g, '');
56
+ }
57
+
58
+ function missingLineDefaults(existingText, defaults) {
59
+ const seen = new Set(
60
+ String(existingText || '')
61
+ .split(/\r?\n/)
62
+ .map(normalizeIgnorePattern)
63
+ .filter(Boolean),
64
+ );
65
+
66
+ return defaults.filter((line) => !seen.has(normalizeIgnorePattern(line)));
67
+ }
68
+
69
+ function appendMissingLines(filePath, lines) {
70
+ const existingText = readTextIfExists(filePath) || '';
71
+ const trimmed = existingText.replace(/\s+$/g, '');
72
+ const prefix = trimmed.length > 0 ? `${trimmed}\n` : '';
73
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
74
+ fs.writeFileSync(filePath, `${prefix}${lines.join('\n')}\n`);
75
+ }
76
+
37
77
  function countNonEmptyLines(text) {
38
78
  return String(text || '')
39
79
  .split(/\r?\n/)
@@ -210,6 +250,82 @@ function countStackInfoLeaks(projectRoot) {
210
250
  return leaks;
211
251
  }
212
252
 
253
+ function collectGeneratedMarkdownFiles(projectRoot) {
254
+ const files = [];
255
+ const rootFiles = ['README.md', 'AGENTS.md'];
256
+
257
+ for (const file of rootFiles) {
258
+ const absolutePath = path.join(projectRoot, file);
259
+ if (fs.existsSync(absolutePath)) {
260
+ files.push(absolutePath);
261
+ }
262
+ }
263
+
264
+ const docsDir = path.join(projectRoot, 'docs');
265
+ if (!fs.existsSync(docsDir)) {
266
+ return files;
267
+ }
268
+
269
+ const walk = (dirPath) => {
270
+ for (const entry of fs.readdirSync(dirPath, { withFileTypes: true })) {
271
+ const fullPath = path.join(dirPath, entry.name);
272
+ if (entry.isDirectory()) {
273
+ walk(fullPath);
274
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
275
+ files.push(fullPath);
276
+ }
277
+ }
278
+ };
279
+
280
+ walk(docsDir);
281
+
282
+ return files;
283
+ }
284
+
285
+ function isExternalLink(target) {
286
+ return /^(?:[a-z][a-z0-9+.-]*:|#)/i.test(target);
287
+ }
288
+
289
+ function normalizeMarkdownLinkTarget(target) {
290
+ return target
291
+ .trim()
292
+ .replace(/^<|>$/g, '')
293
+ .split('#')[0]
294
+ .trim();
295
+ }
296
+
297
+ function collectMissingMarkdownLinks(projectRoot) {
298
+ const missing = [];
299
+ const linkPattern = /!?\[[^\]]*]\(([^)]+)\)/g;
300
+
301
+ for (const filePath of collectGeneratedMarkdownFiles(projectRoot)) {
302
+ const text = readTextIfExists(filePath);
303
+ if (!text) {
304
+ continue;
305
+ }
306
+
307
+ let match;
308
+ while ((match = linkPattern.exec(text)) !== null) {
309
+ const target = normalizeMarkdownLinkTarget(match[1]);
310
+ if (!target || isExternalLink(target)) {
311
+ continue;
312
+ }
313
+
314
+ const resolved = path.resolve(path.dirname(filePath), target);
315
+ const relativeToRoot = path.relative(projectRoot, resolved);
316
+ if (relativeToRoot.startsWith('..') || path.isAbsolute(relativeToRoot)) {
317
+ continue;
318
+ }
319
+
320
+ if (!fs.existsSync(resolved)) {
321
+ missing.push(`${normalizeRelativePath(projectRoot, filePath)} -> ${target}`);
322
+ }
323
+ }
324
+ }
325
+
326
+ return missing;
327
+ }
328
+
213
329
  function collectLayoutReport(projectRoot) {
214
330
  const hasStateMetadata = hasInitializedStateMetadata(readState(projectRoot));
215
331
  const realSlices = readAllSlices(projectRoot);
@@ -276,6 +392,116 @@ function collectLayoutReport(projectRoot) {
276
392
  };
277
393
  }
278
394
 
395
+ function buildDoctorFixPlan(projectRoot) {
396
+ const fixes = [];
397
+ const rootGitignorePath = path.join(projectRoot, '.gitignore');
398
+ const rootGitignoreText = readTextIfExists(rootGitignorePath) || '';
399
+ const missingRootGitignoreLines = missingLineDefaults(rootGitignoreText, ROOT_GITIGNORE_DEFAULTS);
400
+ if (!fs.existsSync(rootGitignorePath)) {
401
+ fixes.push({
402
+ type: 'append-lines',
403
+ path: '.gitignore',
404
+ description: 'Create root .gitignore with safe Quiver defaults.',
405
+ lines: ROOT_GITIGNORE_DEFAULTS,
406
+ });
407
+ } else if (missingRootGitignoreLines.length > 0) {
408
+ fixes.push({
409
+ type: 'append-lines',
410
+ path: '.gitignore',
411
+ description: `Merge missing root .gitignore defaults: ${missingRootGitignoreLines.join(', ')}.`,
412
+ lines: missingRootGitignoreLines,
413
+ });
414
+ }
415
+
416
+ const quiverGitignorePath = path.join(projectRoot, '.quiver', '.gitignore');
417
+ const quiverGitignoreText = readTextIfExists(quiverGitignorePath) || '';
418
+ const quiverDefaults = buildQuiverInternalGitignore().split(/\r?\n/).filter(Boolean);
419
+ const missingQuiverLines = missingLineDefaults(quiverGitignoreText, quiverDefaults);
420
+ if (!fs.existsSync(quiverGitignorePath)) {
421
+ fixes.push({
422
+ type: 'write-json-or-text',
423
+ path: '.quiver/.gitignore',
424
+ description: 'Create internal .quiver/.gitignore for local AI state.',
425
+ content: buildQuiverInternalGitignore(),
426
+ });
427
+ } else if (missingQuiverLines.length > 0) {
428
+ fixes.push({
429
+ type: 'append-lines',
430
+ path: '.quiver/.gitignore',
431
+ description: `Merge missing .quiver/.gitignore defaults: ${missingQuiverLines.join(', ')}.`,
432
+ lines: missingQuiverLines,
433
+ });
434
+ }
435
+
436
+ const configPath = path.join(projectRoot, '.quiver', 'config.json');
437
+ if (!fs.existsSync(configPath)) {
438
+ fixes.push({
439
+ type: 'write-json-or-text',
440
+ path: '.quiver/config.json',
441
+ description: 'Create missing Quiver config metadata.',
442
+ content: `${JSON.stringify(buildQuiverConfig(), null, 2)}\n`,
443
+ });
444
+ }
445
+
446
+ const packageJsonPath = path.join(projectRoot, 'package.json');
447
+ if (fs.existsSync(packageJsonPath)) {
448
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
449
+ const scripts = packageJson.scripts && typeof packageJson.scripts === 'object' ? packageJson.scripts : {};
450
+ const expectedScripts = resolveInitPackageScripts('default');
451
+ const missingScripts = Object.entries(expectedScripts)
452
+ .filter(([name]) => (name.startsWith('quiver:') || name === 'check-handoff') && typeof scripts[name] !== 'string');
453
+
454
+ if (missingScripts.length > 0) {
455
+ fixes.push({
456
+ type: 'merge-package-scripts',
457
+ path: 'package.json',
458
+ description: `Add missing package scripts: ${missingScripts.map(([name]) => name).join(', ')}.`,
459
+ scripts: Object.fromEntries(missingScripts),
460
+ });
461
+ }
462
+ }
463
+
464
+ return fixes;
465
+ }
466
+
467
+ function applyDoctorFixPlan(projectRoot, fixes) {
468
+ for (const fix of fixes) {
469
+ const targetPath = path.join(projectRoot, fix.path);
470
+ if (fix.type === 'append-lines') {
471
+ appendMissingLines(targetPath, fix.lines);
472
+ continue;
473
+ }
474
+
475
+ if (fix.type === 'write-json-or-text') {
476
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
477
+ fs.writeFileSync(targetPath, fix.content);
478
+ continue;
479
+ }
480
+
481
+ if (fix.type === 'merge-package-scripts') {
482
+ const packageJson = JSON.parse(fs.readFileSync(targetPath, 'utf8'));
483
+ packageJson.scripts = {
484
+ ...(packageJson.scripts || {}),
485
+ ...fix.scripts,
486
+ };
487
+ fs.writeFileSync(targetPath, `${JSON.stringify(packageJson, null, 2)}\n`);
488
+ }
489
+ }
490
+ }
491
+
492
+ function formatDoctorFixPlan(fixes, { dryRun = false } = {}) {
493
+ const lines = [dryRun ? 'Quiver doctor fix dry-run' : 'Quiver doctor fix'];
494
+ if (fixes.length === 0) {
495
+ lines.push('- No safe fixes to apply.');
496
+ } else {
497
+ for (const fix of fixes) {
498
+ lines.push(`- ${dryRun ? 'Would update' : 'Updated'} ${fix.path}: ${fix.description}`);
499
+ }
500
+ }
501
+ lines.push('');
502
+ return lines.join('\n');
503
+ }
504
+
279
505
  function collectDoctorReport(projectRoot) {
280
506
  const layout = collectLayoutReport(projectRoot);
281
507
  const warnings = collectDoctorWarnings(projectRoot);
@@ -316,11 +542,19 @@ function collectDoctorWarnings(projectRoot) {
316
542
  warnings.push(`stack information appears outside docs/PROJECT_MAP.md: ${leakIssues.join(', ')}`);
317
543
  }
318
544
 
545
+ const missingLinks = collectMissingMarkdownLinks(projectRoot);
546
+ for (const issue of missingLinks) {
547
+ warnings.push(`missing local docs link: ${issue}`);
548
+ }
549
+
319
550
  return warnings;
320
551
  }
321
552
 
322
553
  module.exports = {
554
+ applyDoctorFixPlan,
555
+ buildDoctorFixPlan,
323
556
  collectDoctorReport,
324
557
  collectDoctorWarnings,
325
558
  collectLayoutReport,
559
+ formatDoctorFixPlan,
326
560
  };
@@ -0,0 +1,115 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { spawnSync } = require('node:child_process');
4
+
5
+ const DEFAULT_OUTPUT_LIMIT = 4000;
6
+
7
+ function redactSecrets(text) {
8
+ return String(text || '')
9
+ .replace(/(authorization:\s*bearer\s+)[^\s`'"]+/gi, '$1[REDACTED]')
10
+ .replace(/\b((?:api[_-]?key|token|secret|password|passwd|pwd)[A-Z0-9_-]*\s*[:=]\s*)[^\s`'"]+/gi, '$1[REDACTED]')
11
+ .replace(/\b(npm_[A-Za-z0-9]{20,})\b/g, '[REDACTED_NPM_TOKEN]');
12
+ }
13
+
14
+ function truncateText(text, maxLength = DEFAULT_OUTPUT_LIMIT) {
15
+ const value = String(text || '');
16
+ if (value.length <= maxLength) {
17
+ return {
18
+ text: value,
19
+ truncated: false,
20
+ };
21
+ }
22
+
23
+ return {
24
+ text: `${value.slice(0, maxLength)}\n[... truncated ${value.length - maxLength} chars ...]`,
25
+ truncated: true,
26
+ };
27
+ }
28
+
29
+ function quoteCommandPart(value) {
30
+ const part = String(value || '');
31
+ return /\s/.test(part) ? JSON.stringify(part) : part;
32
+ }
33
+
34
+ function formatCommand(commandArgs) {
35
+ return commandArgs.map(quoteCommandPart).join(' ');
36
+ }
37
+
38
+ function defaultEvidencePath(repoRoot, startedAt = new Date()) {
39
+ const stamp = startedAt.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z');
40
+ return path.join(repoRoot, '.quiver', 'evidence', `evidence-${stamp}.md`);
41
+ }
42
+
43
+ function renderEvidenceMarkdown(record) {
44
+ return `# Quiver Evidence
45
+
46
+ - Command: \`${record.command}\`
47
+ - Exit code: ${record.exit_code}
48
+ - Duration ms: ${record.duration_ms}
49
+ - Started at: ${record.started_at}
50
+ - Finished at: ${record.finished_at}
51
+ - Output truncated: ${record.output_truncated ? 'yes' : 'no'}
52
+
53
+ ## Stdout
54
+
55
+ \`\`\`\`text
56
+ ${record.stdout || ''}
57
+ \`\`\`\`
58
+
59
+ ## Stderr
60
+
61
+ \`\`\`\`text
62
+ ${record.stderr || ''}
63
+ \`\`\`\`
64
+ `;
65
+ }
66
+
67
+ function runEvidenceCommand(repoRoot, commandArgs, options = {}) {
68
+ if (!Array.isArray(commandArgs) || commandArgs.length === 0) {
69
+ throw new Error('create-quiver: evidence run requires a command after --');
70
+ }
71
+
72
+ const startedAtDate = new Date();
73
+ const started = Date.now();
74
+ const result = (options.spawnSync || spawnSync)(commandArgs[0], commandArgs.slice(1), {
75
+ cwd: repoRoot,
76
+ encoding: 'utf8',
77
+ shell: false,
78
+ });
79
+ const finishedAtDate = new Date();
80
+ const duration = Date.now() - started;
81
+ const exitCode = typeof result.status === 'number' ? result.status : 1;
82
+ const stdout = truncateText(redactSecrets(result.stdout || ''), options.maxOutput || DEFAULT_OUTPUT_LIMIT);
83
+ const stderr = truncateText(redactSecrets(result.stderr || result.error?.message || ''), options.maxOutput || DEFAULT_OUTPUT_LIMIT);
84
+ const record = {
85
+ command: redactSecrets(formatCommand(commandArgs)),
86
+ duration_ms: duration,
87
+ exit_code: exitCode,
88
+ finished_at: finishedAtDate.toISOString(),
89
+ output_truncated: stdout.truncated || stderr.truncated,
90
+ stderr: stderr.text,
91
+ stdout: stdout.text,
92
+ started_at: startedAtDate.toISOString(),
93
+ };
94
+ const outputPath = options.outputPath
95
+ ? path.resolve(repoRoot, options.outputPath)
96
+ : defaultEvidencePath(repoRoot, startedAtDate);
97
+
98
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
99
+ fs.writeFileSync(outputPath, renderEvidenceMarkdown(record));
100
+
101
+ return {
102
+ exitCode,
103
+ outputPath,
104
+ record,
105
+ };
106
+ }
107
+
108
+ module.exports = {
109
+ DEFAULT_OUTPUT_LIMIT,
110
+ defaultEvidencePath,
111
+ redactSecrets,
112
+ renderEvidenceMarkdown,
113
+ runEvidenceCommand,
114
+ truncateText,
115
+ };