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,8 @@ const path = require('node:path');
4
4
  const { spawnSync } = require('node:child_process');
5
5
 
6
6
  const { currentBranch, hasRemote, isCleanWorktree } = require('../git');
7
+ const { parseJsonWithComments } = require('../json');
8
+ const { formatActionableError } = require('../actionable-error');
7
9
 
8
10
  const DEFAULT_GH_COMMAND = 'gh';
9
11
  const DEFAULT_REMOTE = 'origin';
@@ -61,6 +63,60 @@ function formatGhInstallGuidance() {
61
63
  ].join('\n');
62
64
  }
63
65
 
66
+ function quotePosixArg(arg) {
67
+ const value = String(arg);
68
+ return /^[A-Za-z0-9_./:=@-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
69
+ }
70
+
71
+ function quotePowerShellArg(arg) {
72
+ const value = String(arg);
73
+ return /^[A-Za-z0-9_./:=@-]+$/.test(value) ? value : `'${value.replace(/'/g, "''")}'`;
74
+ }
75
+
76
+ function hasShellSensitivePath(...values) {
77
+ return values.some((value) => /\s/.test(String(value || '')));
78
+ }
79
+
80
+ function formatShellPathGuidance(optionName, examplePath) {
81
+ const fallbackPath = examplePath || '~/ssh/github work';
82
+ const windowsFallback = examplePath || '$HOME\\ssh\\github work';
83
+ return [
84
+ 'Path guidance:',
85
+ `- macOS/Linux: ${optionName} ${quotePosixArg(fallbackPath)}`,
86
+ `- Windows PowerShell: ${optionName} ${quotePowerShellArg(windowsFallback)}`,
87
+ `- Git Bash/WSL: ${optionName} ${quotePosixArg(fallbackPath)}`,
88
+ '- Quote paths with spaces; do not remove spaces from real file names.',
89
+ ].join('\n');
90
+ }
91
+
92
+ function formatCommandForShell(command, args, quoter) {
93
+ return `${command} ${args.map(quoter).join(' ')}`;
94
+ }
95
+
96
+ function classifyGhAuthFailure(output) {
97
+ const text = String(output || '').toLowerCase();
98
+ const issues = [];
99
+
100
+ if (/not logged|not authenticated|authentication required|no account/.test(text)) {
101
+ issues.push('no GitHub account is authenticated for this host');
102
+ }
103
+ if (/scope|permission|forbidden|403|oauth/.test(text)) {
104
+ issues.push('the active token may be missing repo/org scopes');
105
+ }
106
+ if (/account|user|login|host/.test(text) && !issues.some((issue) => issue.includes('account'))) {
107
+ issues.push('the active GitHub account or host may not match this repository');
108
+ }
109
+ if (/ssh|identity|alias|public key|permission denied/.test(text)) {
110
+ issues.push('the SSH alias or identity may not match the authenticated GitHub account');
111
+ }
112
+
113
+ if (issues.length === 0) {
114
+ issues.push('GitHub CLI authentication is not usable for this repository yet');
115
+ }
116
+
117
+ return `Likely issue: ${issues.join('; ')}.`;
118
+ }
119
+
64
120
  function createError(code, message, details = {}) {
65
121
  return new GitHubPreflightError(code, message, details);
66
122
  }
@@ -148,7 +204,15 @@ function ensureGhAuthenticated(options = {}) {
148
204
  const details = [stderr, stdout].filter(Boolean).join('\n');
149
205
  throw createError(
150
206
  'GH_NOT_AUTHENTICATED',
151
- `${formatError('gh auth status failed. Run gh auth login and then re-run the preflight.')}${details ? `\n${details}` : ''}`,
207
+ `${formatActionableError({
208
+ failure: 'gh auth status failed. GitHub CLI is not authenticated or the active account/scopes are not usable.',
209
+ impact: 'Quiver cannot verify the GitHub account, repository permissions, or PR readiness.',
210
+ fix: [
211
+ classifyGhAuthFailure(details),
212
+ 'Run `gh auth login`, confirm the expected GitHub account and host, verify repo/org scopes, and if you use --ssh-host-alias run `ssh -T <alias>` to confirm the SSH identity.',
213
+ ].join(' '),
214
+ nextCommand: 'gh auth status',
215
+ })}${details ? `\nDetails:\n${details}` : ''}`,
152
216
  {
153
217
  command,
154
218
  authArgs,
@@ -238,7 +302,12 @@ function ensureIdentityFile(repoRoot, identityFile) {
238
302
  if (!fs.existsSync(resolved)) {
239
303
  throw createError(
240
304
  'MISSING_IDENTITY_FILE',
241
- formatError(`missing SSH identity file at ${resolved}. Check the path you passed as identityFile.`),
305
+ formatActionableError({
306
+ failure: `missing SSH identity file at ${resolved}.`,
307
+ impact: 'Quiver cannot verify the SSH identity that should be used for GitHub PR commands.',
308
+ fix: `Check the path passed with --identity-file and quote it for your shell when it contains spaces.\n${formatShellPathGuidance('--identity-file', normalized)}`,
309
+ nextCommand: 'npx create-quiver ai doctor --dry-run --ssh-host-alias <alias> --identity-file <path>',
310
+ }),
242
311
  {
243
312
  identityFile: normalized,
244
313
  resolvedIdentityFile: resolved,
@@ -249,6 +318,82 @@ function ensureIdentityFile(repoRoot, identityFile) {
249
318
  return resolved;
250
319
  }
251
320
 
321
+ function ensureSshHostAlias(sshHostAlias) {
322
+ const value = String(sshHostAlias || '').trim();
323
+ if (!value) {
324
+ throw createError(
325
+ 'MISSING_SSH_HOST_ALIAS',
326
+ formatActionableError({
327
+ failure: 'missing SSH host alias. Pass --ssh-host-alias <alias> before opening the PR.',
328
+ impact: 'Quiver cannot verify which GitHub SSH identity should be used for this PR flow.',
329
+ fix: 'macOS/Linux/Git Bash/WSL: add a Host entry in ~/.ssh/config, for example `Host github-work`. Windows PowerShell: add the Host entry in $HOME\\.ssh\\config.',
330
+ nextCommand: 'ssh -T <alias>',
331
+ }),
332
+ );
333
+ }
334
+ return value;
335
+ }
336
+
337
+ function prBodySpecDir(repoRoot, prBodyPath) {
338
+ const relative = path.relative(repoRoot, prBodyPath).split(path.sep).join('/');
339
+ const parts = relative.split('/');
340
+ if (parts[0] !== 'specs' || parts.length !== 3 || parts[2] !== 'pr.md') {
341
+ return '';
342
+ }
343
+ return path.join(repoRoot, parts[0], parts[1]);
344
+ }
345
+
346
+ function listOpenSlicesForSpec(specDir) {
347
+ const slicesDir = path.join(specDir, 'slices');
348
+ if (!fs.existsSync(slicesDir)) {
349
+ return [];
350
+ }
351
+
352
+ return fs.readdirSync(slicesDir, { withFileTypes: true })
353
+ .filter((entry) => entry.isDirectory())
354
+ .map((entry) => {
355
+ const slicePath = path.join(slicesDir, entry.name, 'slice.json');
356
+ if (!fs.existsSync(slicePath)) {
357
+ return null;
358
+ }
359
+ const json = parseJsonWithComments(fs.readFileSync(slicePath, 'utf8'));
360
+ const status = String(json.status || 'draft').trim() || 'draft';
361
+ return {
362
+ id: json.slice_id || entry.name,
363
+ status,
364
+ };
365
+ })
366
+ .filter(Boolean)
367
+ .filter((slice) => slice.status !== 'completed')
368
+ .sort((left, right) => left.id.localeCompare(right.id));
369
+ }
370
+
371
+ function ensureNoOpenSlicesForPrBody(repoRoot, prBodyPath) {
372
+ const specDir = prBodySpecDir(repoRoot, prBodyPath);
373
+ if (!specDir) {
374
+ return [];
375
+ }
376
+
377
+ const openSlices = listOpenSlicesForSpec(specDir);
378
+ if (openSlices.length > 0) {
379
+ throw createError(
380
+ 'OPEN_SLICES',
381
+ formatActionableError({
382
+ failure: `cannot create PR while spec slices are still open: ${openSlices.map((slice) => `${slice.id} (${slice.status})`).join(', ')}.`,
383
+ impact: 'The PR would not represent a closed spec and could miss required slice commits or evidence.',
384
+ fix: 'Finish, validate, and close every slice in the spec before creating the PR.',
385
+ nextCommand: 'npx create-quiver ai execute-plan --dry-run --commit',
386
+ }),
387
+ {
388
+ openSlices,
389
+ specDir,
390
+ },
391
+ );
392
+ }
393
+
394
+ return openSlices;
395
+ }
396
+
252
397
  function findPrBodyCandidates(repoRoot) {
253
398
  const candidates = [];
254
399
  const rootPr = path.join(repoRoot, 'pr.md');
@@ -352,6 +497,7 @@ function buildPrCreateArgs(plan) {
352
497
 
353
498
  function buildPrCreatePlan(repoRoot, preflightReport, options = {}) {
354
499
  const prBody = readPrBody(repoRoot, options.prBodyPath || options.input);
500
+ ensureNoOpenSlicesForPrBody(repoRoot, prBody.path);
355
501
  const baseBranch = String(options.baseBranch || 'main').trim() || 'main';
356
502
  const title = String(options.title || '').trim() || extractPrTitle(prBody.body, preflightReport.branchName);
357
503
  const plan = {
@@ -420,6 +566,7 @@ function preflightGitHubPr(repoRoot, options = {}) {
420
566
  const guidePath = ensureGitFlowGuide(repoRoot, options.gitFlowGuidePath);
421
567
  const remote = ensureRemote(repoRoot, options.remote || DEFAULT_REMOTE);
422
568
  const branchName = ensureWorktreeReady(repoRoot, options);
569
+ const sshHostAlias = ensureSshHostAlias(options.sshHostAlias);
423
570
  const identityFile = ensureIdentityFile(repoRoot, options.identityFile);
424
571
 
425
572
  return buildPreflightReport(repoRoot, options, {
@@ -428,6 +575,7 @@ function preflightGitHubPr(repoRoot, options = {}) {
428
575
  guidePath,
429
576
  remote,
430
577
  branchName,
578
+ sshHostAlias,
431
579
  identityFile,
432
580
  });
433
581
  }
@@ -450,6 +598,10 @@ function formatPreflightReport(report, options = {}) {
450
598
  lines.push(`Identity file: ${report.identityFile}`);
451
599
  }
452
600
 
601
+ if (hasShellSensitivePath(report.repoRoot, report.guidePath, report.identityFile)) {
602
+ lines.push(formatShellPathGuidance('--identity-file', report.identityFile || '<path with spaces>'));
603
+ }
604
+
453
605
  lines.push('Checks: gh, gh auth status, git remote, worktree branch, GitFlow guide, SSH identity file');
454
606
 
455
607
  if (dryRun) {
@@ -487,6 +639,12 @@ function formatPrCreateReport({ preflight, plan, result }, options = {}) {
487
639
  lines.push(`Identity file: ${preflight.identityFile}`);
488
640
  }
489
641
 
642
+ if (hasShellSensitivePath(preflight.repoRoot, preflight.identityFile, plan.prBodyPath, ...plan.args)) {
643
+ lines.push('Shell-safe command examples:');
644
+ lines.push(`- macOS/Linux/Git Bash/WSL: ${formatCommandForShell(plan.ghCommand, plan.args, quotePosixArg)}`);
645
+ lines.push(`- Windows PowerShell: ${formatCommandForShell(plan.ghCommand, plan.args, quotePowerShellArg)}`);
646
+ }
647
+
490
648
  if (dryRun) {
491
649
  lines.push('No PR will be created in dry-run mode.');
492
650
  } else if (!create) {
@@ -511,7 +669,9 @@ module.exports = {
511
669
  ensureGhInstalled,
512
670
  ensureGitFlowGuide,
513
671
  ensureIdentityFile,
672
+ ensureNoOpenSlicesForPrBody,
514
673
  ensureRemote,
674
+ ensureSshHostAlias,
515
675
  ensureWorktreeReady,
516
676
  findPrBodyCandidates,
517
677
  formatGhInstallGuidance,
@@ -114,6 +114,119 @@ function markPendingConfirmation(value) {
114
114
  return `Pending confirmation: ${text}`;
115
115
  }
116
116
 
117
+ function readJsonIfExists(filePath) {
118
+ if (!fs.existsSync(filePath)) {
119
+ return null;
120
+ }
121
+
122
+ try {
123
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+
129
+ function detectPackageManager(projectRoot) {
130
+ if (hasPath(projectRoot, 'bun.lockb') || hasPath(projectRoot, 'bun.lock')) return 'bun';
131
+ if (hasPath(projectRoot, 'pnpm-lock.yaml')) return 'pnpm';
132
+ if (hasPath(projectRoot, 'yarn.lock')) return 'yarn';
133
+ return 'npm';
134
+ }
135
+
136
+ function detectSourceDirectories(projectRoot) {
137
+ const names = ['src', 'app', 'apps', 'packages', 'lib', 'server', 'client', 'web'];
138
+ return names.filter((name) => {
139
+ try {
140
+ return fs.statSync(path.join(projectRoot, name)).isDirectory();
141
+ } catch {
142
+ return false;
143
+ }
144
+ });
145
+ }
146
+
147
+ function collectRootNames(projectRoot) {
148
+ try {
149
+ return fs.readdirSync(projectRoot, { withFileTypes: true })
150
+ .filter((entry) => !entry.name.startsWith('.') && entry.name !== 'node_modules')
151
+ .map((entry) => `${entry.name}${entry.isDirectory() ? '/' : ''}`)
152
+ .slice(0, 20);
153
+ } catch {
154
+ return [];
155
+ }
156
+ }
157
+
158
+ function detectStackSummary(packageJson, projectRoot) {
159
+ const dependencies = {
160
+ ...(packageJson?.dependencies || {}),
161
+ ...(packageJson?.devDependencies || {}),
162
+ };
163
+ const signals = [];
164
+
165
+ if (dependencies.next || hasPath(projectRoot, 'next.config.js') || hasPath(projectRoot, 'next.config.mjs')) signals.push('Next.js');
166
+ if (dependencies.vite || hasPath(projectRoot, 'vite.config.js') || hasPath(projectRoot, 'vite.config.ts')) signals.push('Vite');
167
+ if (dependencies.react) signals.push('React');
168
+ if (dependencies.vue) signals.push('Vue');
169
+ if (dependencies.angular || dependencies['@angular/core'] || hasPath(projectRoot, 'angular.json')) signals.push('Angular');
170
+ if (dependencies.svelte || hasPath(projectRoot, 'svelte.config.js')) signals.push('Svelte');
171
+ if (dependencies.express) signals.push('Express');
172
+ if (hasPath(projectRoot, 'pyproject.toml') || hasPath(projectRoot, 'requirements.txt')) signals.push('Python');
173
+ if (hasPath(projectRoot, 'go.mod')) signals.push('Go');
174
+
175
+ return signals.length > 0 ? signals.join(', ') : 'Pending confirmation: no primary stack could be inferred from root signals.';
176
+ }
177
+
178
+ function collectProjectFacts(projectRoot) {
179
+ const packageJson = readJsonIfExists(path.join(projectRoot, 'package.json'));
180
+ const scripts = packageJson?.scripts && typeof packageJson.scripts === 'object' ? packageJson.scripts : {};
181
+ const packageManager = packageJson?.packageManager
182
+ ? String(packageJson.packageManager).split('@')[0]
183
+ : detectPackageManager(projectRoot);
184
+
185
+ return {
186
+ packageJsonPresent: Boolean(packageJson),
187
+ packageManager,
188
+ stackSummary: detectStackSummary(packageJson, projectRoot),
189
+ scripts,
190
+ rootNames: collectRootNames(projectRoot),
191
+ sourceDirectories: detectSourceDirectories(projectRoot),
192
+ commands: {
193
+ install: packageManager === 'pnpm' ? 'pnpm install' : packageManager === 'yarn' ? 'yarn install' : packageManager === 'bun' ? 'bun install' : 'npm install',
194
+ dev: scripts.dev || scripts.start || 'Pending confirmation: no dev/start script detected.',
195
+ build: scripts.build || 'Pending confirmation: no build script detected.',
196
+ test: scripts.test || 'Pending confirmation: no test script detected.',
197
+ lint: scripts.lint || 'Pending confirmation: no lint script detected.',
198
+ },
199
+ };
200
+ }
201
+
202
+ function readProjectMapField(projectRoot, label) {
203
+ const filePath = path.join(projectRoot, 'docs', 'PROJECT_MAP.md');
204
+ if (!fs.existsSync(filePath)) {
205
+ return '';
206
+ }
207
+
208
+ const text = fs.readFileSync(filePath, 'utf8');
209
+ const escaped = label.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
210
+ const match = text.match(new RegExp(`^- ${escaped}:\\s*(.+)$`, 'mi'));
211
+ return match ? match[1].trim() : '';
212
+ }
213
+
214
+ function collectContextContradictions(projectRoot, plan, facts) {
215
+ const contradictions = [];
216
+ const mappedName = readProjectMapField(projectRoot, 'Name');
217
+ const mappedPackageManager = readProjectMapField(projectRoot, 'Package manager');
218
+
219
+ if (mappedName && mappedName !== plan.projectName) {
220
+ contradictions.push(`docs/PROJECT_MAP.md reports project name '${mappedName}', but package/root identity resolves to '${plan.projectName}'.`);
221
+ }
222
+
223
+ if (mappedPackageManager && mappedPackageManager !== facts.packageManager) {
224
+ contradictions.push(`docs/PROJECT_MAP.md reports package manager '${mappedPackageManager}', but current root signals resolve to '${facts.packageManager}'.`);
225
+ }
226
+
227
+ return contradictions.map(markPendingConfirmation);
228
+ }
229
+
117
230
  function formatDocStatusLines(items) {
118
231
  if (!Array.isArray(items) || items.length === 0) {
119
232
  return ['- none'];
@@ -182,6 +295,7 @@ function collectOnboardingContextPlan(projectRoot) {
182
295
  function collectContextPreparationPlan(projectRoot) {
183
296
  const onboardingPlan = collectOnboardingContextPlan(projectRoot);
184
297
  const identity = resolveProjectIdentity(projectRoot);
298
+ const facts = collectProjectFacts(projectRoot);
185
299
  const filesConsidered = CONTEXT_PREP_SOURCE_DOCS.map(([relativePath, reason]) => ({
186
300
  path: relativePath,
187
301
  reason,
@@ -202,8 +316,7 @@ function collectContextPreparationPlan(projectRoot) {
202
316
  ? 'Pending confirmation: docs/INDEX.md is missing, so navigation should stay conservative and index-first.'
203
317
  : null,
204
318
  ]);
205
-
206
- return {
319
+ const plan = {
207
320
  ...onboardingPlan,
208
321
  ...identity,
209
322
  approvedDocPaths: getPreparedContextDocPaths(),
@@ -211,6 +324,12 @@ function collectContextPreparationPlan(projectRoot) {
211
324
  omittedPaths: onboardingPlan.omittedByDefault.slice(),
212
325
  assumptions,
213
326
  risks,
327
+ facts,
328
+ };
329
+
330
+ return {
331
+ ...plan,
332
+ contradictions: collectContextContradictions(projectRoot, plan, facts),
214
333
  };
215
334
  }
216
335
 
@@ -230,6 +349,9 @@ function buildContextPreparationNotes(plan) {
230
349
  '### Risks',
231
350
  ...formatSimpleBullets(plan.risks, 'none'),
232
351
  '',
352
+ '### Contradictions',
353
+ ...formatSimpleBullets(plan.contradictions, 'none'),
354
+ '',
233
355
  '### Omitted Paths',
234
356
  ...formatSimpleBullets(plan.omittedPaths, 'none'),
235
357
  ];
@@ -247,6 +369,76 @@ function readTemplate(relativePath) {
247
369
  return fs.readFileSync(path.join(PACKAGE_ROOT, relativePath), 'utf8');
248
370
  }
249
371
 
372
+ function renderProjectMapDraft(plan) {
373
+ const facts = plan.facts;
374
+ const lines = [
375
+ '# Project Map',
376
+ '',
377
+ 'This file was prepared by `npx create-quiver ai prepare-context`.',
378
+ 'Run `npx create-quiver analyze` to refresh it with a deeper repository scan.',
379
+ '',
380
+ '## Project',
381
+ `- Name: ${plan.projectName}`,
382
+ `- Slug: ${plan.projectSlug}`,
383
+ `- Package manager: ${facts.packageManager}`,
384
+ `- package.json present: ${facts.packageJsonPresent ? 'yes' : 'no'}`,
385
+ `- Stack summary: ${facts.stackSummary}`,
386
+ '',
387
+ '## Commands',
388
+ `- Install: ${facts.commands.install}`,
389
+ `- Dev: ${facts.commands.dev}`,
390
+ `- Build: ${facts.commands.build}`,
391
+ `- Test: ${facts.commands.test}`,
392
+ `- Lint: ${facts.commands.lint}`,
393
+ '',
394
+ '## Structure',
395
+ `- Source directories: ${facts.sourceDirectories.length > 0 ? facts.sourceDirectories.join(', ') : 'Pending confirmation: no common source directory detected.'}`,
396
+ `- Root entries: ${facts.rootNames.length > 0 ? facts.rootNames.join(', ') : 'Pending confirmation: root entries could not be listed.'}`,
397
+ '',
398
+ '## Assumptions',
399
+ ...formatSimpleBullets(plan.assumptions, 'none'),
400
+ '',
401
+ '## Risks',
402
+ ...formatSimpleBullets(plan.risks, 'none'),
403
+ '',
404
+ '## Contradictions',
405
+ ...formatSimpleBullets(plan.contradictions, 'none'),
406
+ ];
407
+
408
+ return `${lines.join('\n')}\n`;
409
+ }
410
+
411
+ function renderArchitectureDraft(plan) {
412
+ const facts = plan.facts;
413
+ const lines = [
414
+ `# ${plan.projectName} Architecture`,
415
+ '',
416
+ 'This document captures only what Quiver can infer safely from repository structure and docs.',
417
+ '',
418
+ '## Current Understanding',
419
+ `- Stack: ${facts.stackSummary}`,
420
+ `- Source directories: ${facts.sourceDirectories.length > 0 ? facts.sourceDirectories.join(', ') : 'Pending confirmation: no common source directory detected.'}`,
421
+ `- Package manager: ${facts.packageManager}`,
422
+ '',
423
+ '## Boundaries',
424
+ '- TODO: confirm application boundaries with the team.',
425
+ '- Pending confirmation: no architecture decision should be treated as approved unless it appears in `docs/DECISIONS.md` or an approved spec.',
426
+ '',
427
+ '## Commands That Shape Architecture',
428
+ `- Build: ${facts.commands.build}`,
429
+ `- Test: ${facts.commands.test}`,
430
+ `- Lint: ${facts.commands.lint}`,
431
+ '',
432
+ '## Risks',
433
+ ...formatSimpleBullets(plan.risks, 'none'),
434
+ '',
435
+ '## Contradictions',
436
+ ...formatSimpleBullets(plan.contradictions, 'none'),
437
+ ];
438
+
439
+ return `${lines.join('\n')}\n`;
440
+ }
441
+
250
442
  function buildContextPreparationDrafts(projectRoot) {
251
443
  const plan = collectContextPreparationPlan(projectRoot);
252
444
  const currentDate = new Date().toISOString().slice(0, 10);
@@ -257,6 +449,11 @@ function buildContextPreparationDrafts(projectRoot) {
257
449
  estado: 'En preparación',
258
450
  fase: 'Fase 0',
259
451
  progress: 0,
452
+ packageManager: plan.facts.packageManager,
453
+ stackSummary: plan.facts.stackSummary,
454
+ primaryInstall: plan.facts.commands.install,
455
+ primaryDev: plan.facts.commands.dev,
456
+ primaryTest: plan.facts.commands.test,
260
457
  };
261
458
  const notes = buildContextPreparationNotes(plan);
262
459
  const decisionSection = [
@@ -266,6 +463,14 @@ function buildContextPreparationDrafts(projectRoot) {
266
463
  `| ${currentDate} | ai prepare-context must remain docs-only | Keeps context prep from touching product code. | Broader write targets | Draft generation stays safe and reviewable |`,
267
464
  ].join('\n');
268
465
  const docs = [
466
+ {
467
+ path: 'docs/INDEX.md',
468
+ content: appendNotes(renderTemplate(readTemplate('docs/INDEX.md.template'), replacements), notes),
469
+ },
470
+ {
471
+ path: 'docs/PROJECT_MAP.md',
472
+ content: appendNotes(renderProjectMapDraft(plan), notes),
473
+ },
269
474
  {
270
475
  path: 'docs/AI_CONTEXT.md',
271
476
  content: appendNotes(renderTemplate(readTemplate('docs/AI_CONTEXT.md.template'), replacements), notes),
@@ -278,6 +483,14 @@ function buildContextPreparationDrafts(projectRoot) {
278
483
  path: 'docs/CONTEXTO.md',
279
484
  content: appendNotes(renderTemplate(readTemplate('docs/CONTEXTO.md.template'), replacements), notes),
280
485
  },
486
+ {
487
+ path: 'docs/WORKFLOW.md',
488
+ content: appendNotes(renderTemplate(readTemplate('docs/WORKFLOW.md.template'), replacements), notes),
489
+ },
490
+ {
491
+ path: 'docs/ARCHITECTURE.md',
492
+ content: appendNotes(renderArchitectureDraft(plan), notes),
493
+ },
281
494
  {
282
495
  path: 'docs/STATUS.md',
283
496
  content: appendNotes(renderTemplate(readTemplate('docs/STATUS.md.template'), replacements), notes),
@@ -192,6 +192,8 @@ function savePlanReview(projectRoot, options = {}) {
192
192
  source_kind: options.inputKind || null,
193
193
  source_version: options.inputVersion || null,
194
194
  path: toRelativePosix(projectRoot, reviewPath),
195
+ raw_artifact_path: options.rawArtifactPath || null,
196
+ output_source: options.outputSource || null,
195
197
  reviewed_at: now,
196
198
  };
197
199
  fs.writeFileSync(planReviewMetaPath(projectRoot), `${JSON.stringify(meta, null, 2)}\n`);
@@ -208,8 +210,11 @@ function assertPlanReviewed(projectRoot) {
208
210
  if (review.status !== 'reviewed') {
209
211
  const nextCommand = review.status === 'unapproved'
210
212
  ? 'npx create-quiver ai approve --phase technical-plan --version <n>'
211
- : 'npx create-quiver ai review-plan';
212
- throw new Error(formatError(`ai plan phase 'spec' requires a reviewed and approved technical-plan input; current review status: ${review.status}. Run \`${nextCommand}\`.`));
213
+ : 'npx create-quiver ai review-plan --dry-run';
214
+ const followUp = review.status === 'unapproved'
215
+ ? ''
216
+ : ' Preview the review first, then run `npx create-quiver ai review-plan` to persist it.';
217
+ throw new Error(formatError(`ai plan phase 'spec' requires a reviewed and approved technical-plan input; current review status: ${review.status}. Run \`${nextCommand}\`.${followUp}`));
213
218
  }
214
219
  return review;
215
220
  }
@@ -2,6 +2,7 @@ const fs = require('node:fs');
2
2
  const { spawn } = require('node:child_process');
3
3
 
4
4
  const { finalizePromptTransport, preparePromptTransport, describePromptTransport } = require('./prompt-transport');
5
+ const { redactSecrets } = require('../evidence');
5
6
 
6
7
  const SUPPORTED_PROVIDERS = ['codex', 'claude', 'gemini'];
7
8
 
@@ -107,7 +108,7 @@ function serializeError(error, provider, invocation) {
107
108
 
108
109
  return {
109
110
  code: error.code || 'PROVIDER_ERROR',
110
- message: error.message || String(error),
111
+ message: redactSecrets(error.message || String(error)),
111
112
  provider,
112
113
  command: invocation.command,
113
114
  args: invocation.args.slice(),
@@ -178,8 +179,8 @@ function runSpawn(command, args, options = {}) {
178
179
  ok: payload.exitCode === 0,
179
180
  exitCode: payload.exitCode,
180
181
  signal: payload.signal || null,
181
- stdout,
182
- stderr,
182
+ stdout: redactSecrets(stdout),
183
+ stderr: redactSecrets(stderr),
183
184
  error: payload.error ? serializeError(payload.error, options.provider, options.invocation) : null,
184
185
  });
185
186
  };