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.
- package/CHANGELOG.md +52 -0
- package/README.md +65 -25
- package/README_FOR_AI.md +36 -29
- package/ROADMAP.md +22 -3
- package/docs/AI_ONBOARDING_PROMPT.md.template +7 -1
- package/docs/COMMANDS.md.template +53 -20
- package/docs/STATUS.md.template +5 -1
- package/docs/WORKFLOW.md.template +13 -11
- package/package.json +10 -3
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/EVIDENCE_REPORT.md +293 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/EXECUTION_PLAN.md +58 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/SPEC.md +242 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/STATUS.md +35 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/pr.md +77 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-00-spec-foundation/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-01-cli-contract-compatibility/slice.json +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/CLOSURE_BRIEF.md +43 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-02-run-state-phase-locks/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-03-safe-ai-onboarding-docs/slice.json +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-04-agent-profiles-adapters/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-05-approval-gates/slice.json +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-06-spec-slice-generator/slice.json +55 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-07-slice-execution-planner/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/CLOSURE_BRIEF.md +39 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-08-controlled-slice-execution/slice.json +53 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-09-git-worktree-pr-lifecycle/slice.json +52 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/CLOSURE_BRIEF.md +39 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-10-validation-errors-fixtures/slice.json +56 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v25-ai-first-lifecycle-orchestrator/slices/slice-11-export-dashboard-migration/slice.json +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/EVIDENCE_REPORT.md +208 -0
- package/specs/quiver-v26-0121-smoke-hardening/EXECUTION_PLAN.md +57 -0
- package/specs/quiver-v26-0121-smoke-hardening/SPEC.md +137 -0
- package/specs/quiver-v26-0121-smoke-hardening/STATUS.md +32 -0
- package/specs/quiver-v26-0121-smoke-hardening/pr.md +96 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-00-docs-foundation/slice.json +73 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/EXECUTION_BRIEF.md +51 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-01-cli-help-version-contract/slice.json +76 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-02-init-doc-links-and-flow-guidance/slice.json +75 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-03-ai-approval-review-consistency/slice.json +77 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-04-local-validation-brief-contracts/slice.json +77 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-05-demo-scaffold-readiness/slice.json +84 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-06-plan-graph-scope-performance/slice.json +82 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v26-0121-smoke-hardening/slices/slice-07-smoke-release-readiness/slice.json +92 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/AUDIT_V24_V25_V26.md +67 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/COMMAND_CONTRACTS.md +125 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/COVERAGE_MATRIX.md +74 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/EVIDENCE_REPORT.md +179 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/EXECUTION_PLAN.md +71 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/SPEC.md +176 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/STATUS.md +37 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/pr.md +132 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/slice.json +75 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/CLOSURE_BRIEF.md +37 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/slice.json +79 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/EXECUTION_BRIEF.md +54 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/slice.json +75 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/CLOSURE_BRIEF.md +36 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/slice.json +78 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/slice.json +77 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/slice.json +84 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/slice.json +99 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/EXECUTION_BRIEF.md +57 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/slice.json +88 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/CLOSURE_BRIEF.md +31 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/slice.json +85 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/slice.json +91 -0
- package/src/create-quiver/commands/ai.js +652 -27
- package/src/create-quiver/commands/flow.js +58 -9
- package/src/create-quiver/commands/graph.js +11 -9
- package/src/create-quiver/commands/plan.js +7 -16
- package/src/create-quiver/commands/spec.js +282 -0
- package/src/create-quiver/index.js +409 -31
- package/src/create-quiver/lib/actionable-error.js +27 -0
- package/src/create-quiver/lib/agent-profiles.js +16 -4
- package/src/create-quiver/lib/ai/artifacts.js +318 -0
- package/src/create-quiver/lib/ai/context-packs.js +4 -0
- package/src/create-quiver/lib/ai/execution-plan.js +16 -1
- package/src/create-quiver/lib/ai/executor.js +272 -21
- package/src/create-quiver/lib/ai/export-state.js +679 -0
- package/src/create-quiver/lib/ai/github.js +162 -2
- package/src/create-quiver/lib/ai/onboarding-template.js +215 -2
- package/src/create-quiver/lib/ai/plan-review.js +7 -2
- package/src/create-quiver/lib/ai/providers.js +4 -3
- package/src/create-quiver/lib/ai/run-state.js +414 -0
- package/src/create-quiver/lib/ai/spec-generator.js +84 -13
- package/src/create-quiver/lib/ai/spec-templates.js +150 -21
- package/src/create-quiver/lib/analyze.js +2 -2
- package/src/create-quiver/lib/approvals.js +36 -5
- package/src/create-quiver/lib/demo.js +189 -14
- package/src/create-quiver/lib/doctor.js +154 -0
- package/src/create-quiver/lib/git.js +40 -1
- package/src/create-quiver/lib/handoff.js +123 -12
- package/src/create-quiver/lib/init-docs.js +35 -13
- package/src/create-quiver/lib/init-layout.js +9 -0
- package/src/create-quiver/lib/json.js +53 -3
- package/src/create-quiver/lib/lifecycle.js +52 -3
- package/src/create-quiver/lib/locks.js +134 -0
- package/src/create-quiver/lib/package-safety.js +7 -0
- package/src/create-quiver/lib/paths.js +74 -0
- package/src/create-quiver/lib/project-scan.js +74 -0
- package/src/create-quiver/lib/project-state-resolver.js +236 -0
- package/src/create-quiver/lib/readiness.js +66 -10
- package/src/create-quiver/lib/scope.js +52 -8
- package/src/create-quiver/lib/slice-graph.js +138 -38
- package/src/create-quiver/lib/slice.js +14 -5
- package/src/create-quiver/lib/spec-worktrees.js +129 -32
- 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
|
-
`${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|