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
|
@@ -5,9 +5,11 @@ const cp = require('node:child_process');
|
|
|
5
5
|
const { buildContextPackMetadata, normalizeRole } = require('./context-packs');
|
|
6
6
|
const { buildProviderInvocation, runProvider } = require('./providers');
|
|
7
7
|
const { resolveProfileProvider } = require('../agent-profiles');
|
|
8
|
-
const { runGit } = require('../git');
|
|
8
|
+
const { currentBranch, runGit } = require('../git');
|
|
9
|
+
const { redactSecrets, truncateText } = require('../evidence');
|
|
9
10
|
const { captureWorktreeSnapshot, validateScopeSnapshot } = require('../scope');
|
|
10
11
|
const { resolveSliceContext } = require('../slice');
|
|
12
|
+
const { validateProjectRelativePaths } = require('../paths');
|
|
11
13
|
|
|
12
14
|
const DEFAULT_EXECUTE_PROVIDER = 'codex';
|
|
13
15
|
const DEFAULT_EXECUTE_ROLE = 'executor';
|
|
@@ -77,6 +79,14 @@ function formatList(items) {
|
|
|
77
79
|
return items.map((item) => `- ${item}`);
|
|
78
80
|
}
|
|
79
81
|
|
|
82
|
+
function uniqueList(items) {
|
|
83
|
+
return Array.from(new Set((Array.isArray(items) ? items : []).map((item) => String(item)).filter(Boolean)));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function escapeRegex(value) {
|
|
87
|
+
return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
function extractMarkdownHeading(text) {
|
|
81
91
|
const match = String(text || '').match(/^#\s+(.+)$/m);
|
|
82
92
|
return match ? match[1].trim() : '';
|
|
@@ -189,6 +199,9 @@ function buildManualExecutorPrompt({ repoRoot, slicePath, role, context, tokenLi
|
|
|
189
199
|
`- Source: ${specExcerpt.path}`,
|
|
190
200
|
...specExcerpt.lines,
|
|
191
201
|
'',
|
|
202
|
+
'Expected read paths:',
|
|
203
|
+
...formatList(executorContext.expectedReadPaths),
|
|
204
|
+
'',
|
|
192
205
|
'Allowed files:',
|
|
193
206
|
...formatList(executorContext.allowedFiles),
|
|
194
207
|
'',
|
|
@@ -201,6 +214,9 @@ function buildManualExecutorPrompt({ repoRoot, slicePath, role, context, tokenLi
|
|
|
201
214
|
'Validation commands:',
|
|
202
215
|
...formatList(executorContext.validationCommands),
|
|
203
216
|
'',
|
|
217
|
+
'Validation hints:',
|
|
218
|
+
...formatList(executorContext.validationHints),
|
|
219
|
+
'',
|
|
204
220
|
'Exact deliverable expected:',
|
|
205
221
|
'- Implement only this slice.',
|
|
206
222
|
'- Keep the change inside the allowed files.',
|
|
@@ -288,9 +304,11 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
|
|
|
288
304
|
});
|
|
289
305
|
const relativeSlicePath = toRelativePath(canonicalRepoRoot, slice.sliceAbs);
|
|
290
306
|
const relativeBriefPath = toRelativePath(canonicalRepoRoot, briefPath);
|
|
291
|
-
const allowedFiles = Array.isArray(slice.files) ? slice.files.map((file) => String(file)) : [];
|
|
307
|
+
const allowedFiles = validateProjectRelativePaths(Array.isArray(slice.files) ? slice.files.map((file) => String(file)) : [], 'slice write scope');
|
|
308
|
+
const expectedReadPaths = validateProjectRelativePaths(Array.isArray(slice.expectedReadPaths) ? slice.expectedReadPaths.map((file) => String(file)) : [], 'slice read scope');
|
|
292
309
|
const acceptance = Array.isArray(slice.acceptance) ? slice.acceptance.map((item) => String(item)) : [];
|
|
293
310
|
const validationCommands = Array.isArray(slice.tests) ? slice.tests.map((item) => String(item)) : [];
|
|
311
|
+
const validationHints = Array.isArray(slice.validationHints) ? slice.validationHints.map((item) => String(item)) : [];
|
|
294
312
|
const mustItems = Array.isArray(slice.json.must) ? slice.json.must.map((item) => String(item)) : [];
|
|
295
313
|
const excludedItems = Array.isArray(slice.json.not_included) ? slice.json.not_included.map((item) => String(item)) : [];
|
|
296
314
|
|
|
@@ -301,6 +319,8 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
|
|
|
301
319
|
`Spec: ${slice.specSlug}`,
|
|
302
320
|
`Slice file: ${relativeSlicePath}`,
|
|
303
321
|
`Execution brief: ${relativeBriefPath}`,
|
|
322
|
+
'Expected read paths:',
|
|
323
|
+
...formatList(expectedReadPaths),
|
|
304
324
|
'Allowed files:',
|
|
305
325
|
...formatList(allowedFiles),
|
|
306
326
|
'Acceptance criteria:',
|
|
@@ -317,6 +337,10 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
|
|
|
317
337
|
sections.push('Not included:', ...formatList(excludedItems));
|
|
318
338
|
}
|
|
319
339
|
|
|
340
|
+
if (validationHints.length > 0) {
|
|
341
|
+
sections.push('Validation hints:', ...formatList(validationHints));
|
|
342
|
+
}
|
|
343
|
+
|
|
320
344
|
sections.push(
|
|
321
345
|
'Constraints:',
|
|
322
346
|
'- Do not commit manually. Quiver can create the slice commit after scope and validation pass when the user enables --commit.',
|
|
@@ -332,8 +356,10 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
|
|
|
332
356
|
briefPath: relativeBriefPath,
|
|
333
357
|
briefText,
|
|
334
358
|
context: pack,
|
|
359
|
+
expectedReadPaths,
|
|
335
360
|
prompt: sections.join('\n\n'),
|
|
336
361
|
slice,
|
|
362
|
+
validationHints,
|
|
337
363
|
validationCommands,
|
|
338
364
|
};
|
|
339
365
|
}
|
|
@@ -407,18 +433,18 @@ function runValidationCommand(command, repoRoot) {
|
|
|
407
433
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
408
434
|
});
|
|
409
435
|
return {
|
|
410
|
-
command,
|
|
436
|
+
command: redactSecrets(command),
|
|
411
437
|
ok: true,
|
|
412
|
-
stdout,
|
|
438
|
+
stdout: redactSecrets(stdout),
|
|
413
439
|
stderr: '',
|
|
414
440
|
exitCode: 0,
|
|
415
441
|
};
|
|
416
442
|
} catch (error) {
|
|
417
443
|
return {
|
|
418
|
-
command,
|
|
444
|
+
command: redactSecrets(command),
|
|
419
445
|
ok: false,
|
|
420
|
-
stdout: error.stdout ? String(error.stdout) : '',
|
|
421
|
-
stderr: error.stderr ? String(error.stderr) : '',
|
|
446
|
+
stdout: redactSecrets(error.stdout ? String(error.stdout) : ''),
|
|
447
|
+
stderr: redactSecrets(error.stderr ? String(error.stderr) : ''),
|
|
422
448
|
exitCode: Number.isInteger(error.status) ? error.status : 1,
|
|
423
449
|
error,
|
|
424
450
|
};
|
|
@@ -428,7 +454,13 @@ function runValidationCommand(command, repoRoot) {
|
|
|
428
454
|
function runValidationCommands(repoRoot, commands, runner = runValidationCommand) {
|
|
429
455
|
const results = [];
|
|
430
456
|
for (const command of commands) {
|
|
431
|
-
const
|
|
457
|
+
const rawResult = runner(command, repoRoot);
|
|
458
|
+
const result = {
|
|
459
|
+
...rawResult,
|
|
460
|
+
command: redactSecrets(rawResult.command || command),
|
|
461
|
+
stderr: redactSecrets(rawResult.stderr || ''),
|
|
462
|
+
stdout: redactSecrets(rawResult.stdout || ''),
|
|
463
|
+
};
|
|
432
464
|
results.push(result);
|
|
433
465
|
if (!result.ok) {
|
|
434
466
|
const details = [
|
|
@@ -492,11 +524,196 @@ function commitSliceChanges(repoRoot, slice, changedFiles, options = {}) {
|
|
|
492
524
|
};
|
|
493
525
|
}
|
|
494
526
|
|
|
527
|
+
function assertCorrectSliceWorktree(repoRoot, slice, options = {}) {
|
|
528
|
+
if (options.skipWorktreeBranchCheck === true) {
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const expectedBranch = String(slice.branchName || slice.json.git?.branch_name || '').trim();
|
|
533
|
+
if (!expectedBranch) {
|
|
534
|
+
return null;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const actualBranch = currentBranch(repoRoot);
|
|
538
|
+
if (actualBranch !== expectedBranch) {
|
|
539
|
+
const error = new Error(formatError(`ai execute-slice must run from the slice worktree branch. Current branch: ${actualBranch || '(detached or unavailable)'}. Expected: ${expectedBranch}.`));
|
|
540
|
+
error.code = 'WRONG_WORKTREE';
|
|
541
|
+
error.details = {
|
|
542
|
+
actualBranch,
|
|
543
|
+
expectedBranch,
|
|
544
|
+
slice: slice.sliceRel,
|
|
545
|
+
};
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return {
|
|
550
|
+
actualBranch,
|
|
551
|
+
expectedBranch,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function sliceLifecycleArtifactPaths(repoRoot, slice) {
|
|
556
|
+
const closureAbs = path.join(path.dirname(slice.sliceAbs), 'CLOSURE_BRIEF.md');
|
|
557
|
+
return {
|
|
558
|
+
closure: toRelativePath(repoRoot, closureAbs),
|
|
559
|
+
commandLog: toRelativePath(repoRoot, path.join(slice.specDirAbs, 'COMMAND_LOG.md')),
|
|
560
|
+
evidence: toRelativePath(repoRoot, path.join(slice.specDirAbs, 'EVIDENCE_REPORT.md')),
|
|
561
|
+
sliceJson: toRelativePath(repoRoot, slice.sliceAbs),
|
|
562
|
+
status: toRelativePath(repoRoot, path.join(slice.specDirAbs, 'STATUS.md')),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function renderClosureBrief({ slice, changedFiles, validationResults, completedAt }) {
|
|
567
|
+
const criteria = Array.isArray(slice.acceptance) ? slice.acceptance : [];
|
|
568
|
+
const validationLines = Array.isArray(validationResults) && validationResults.length > 0
|
|
569
|
+
? validationResults.map((result) => `- [x] \`${result.command}\` exited ${result.exitCode}`)
|
|
570
|
+
: ['- [x] No validation commands declared.'];
|
|
571
|
+
|
|
572
|
+
return `${[
|
|
573
|
+
`# CLOSURE BRIEF - ${slice.sliceId}: ${slice.json.title || slice.sliceId}`,
|
|
574
|
+
'',
|
|
575
|
+
'## Summary of Work',
|
|
576
|
+
'',
|
|
577
|
+
`Executed controlled slice closure at ${completedAt}. Quiver validated scope, validation commands, and lifecycle evidence for this slice.`,
|
|
578
|
+
'',
|
|
579
|
+
'## Validation Against Acceptance Criteria',
|
|
580
|
+
'',
|
|
581
|
+
...(criteria.length > 0 ? criteria.map((item) => `- [x] ${item}`) : ['- [x] Slice execution completed with scope validation.']),
|
|
582
|
+
'',
|
|
583
|
+
'## Relevant Changes',
|
|
584
|
+
'',
|
|
585
|
+
...formatList(changedFiles),
|
|
586
|
+
'',
|
|
587
|
+
'## Validation Commands',
|
|
588
|
+
'',
|
|
589
|
+
...validationLines,
|
|
590
|
+
'',
|
|
591
|
+
'## Pending',
|
|
592
|
+
'',
|
|
593
|
+
'None recorded by Quiver.',
|
|
594
|
+
'',
|
|
595
|
+
'## Remaining Risks',
|
|
596
|
+
'',
|
|
597
|
+
'None recorded by Quiver.',
|
|
598
|
+
'',
|
|
599
|
+
'## Future Recommendations',
|
|
600
|
+
'',
|
|
601
|
+
'Review the evidence report and commit diff before opening the PR.',
|
|
602
|
+
'',
|
|
603
|
+
].join('\n')}\n`;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function appendSection(filePath, fallbackTitle, section) {
|
|
607
|
+
const current = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8').trimEnd() : fallbackTitle;
|
|
608
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
609
|
+
fs.writeFileSync(filePath, `${current}\n\n${section.trimEnd()}\n`);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function updateStatusMarkdown(filePath, slice, completedAt) {
|
|
613
|
+
const fallback = `# Status - ${slice.specSlug}\n`;
|
|
614
|
+
let text = fs.existsSync(filePath) ? fs.readFileSync(filePath, 'utf8') : fallback;
|
|
615
|
+
const rowRegex = new RegExp(`(\\|\\s*${escapeRegex(slice.sliceId)}\\s*\\|\\s*)[^|\\n]+(\\|[^\\n]*\\|)`);
|
|
616
|
+
if (rowRegex.test(text)) {
|
|
617
|
+
text = text.replace(rowRegex, '$1Completed $2');
|
|
618
|
+
}
|
|
619
|
+
text = text.replace(/\*\*Current slice:\*\*\s*[^\n]*/i, `**Current slice:** ${slice.sliceId} completed`);
|
|
620
|
+
if (!text.endsWith('\n')) {
|
|
621
|
+
text += '\n';
|
|
622
|
+
}
|
|
623
|
+
const section = [
|
|
624
|
+
'',
|
|
625
|
+
`## Execution Update - ${slice.sliceId}`,
|
|
626
|
+
'',
|
|
627
|
+
`- Status: Completed`,
|
|
628
|
+
`- Completed at: ${completedAt}`,
|
|
629
|
+
`- Source: \`npx create-quiver ai execute-slice --slice ${slice.sliceRel}\``,
|
|
630
|
+
].join('\n');
|
|
631
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
632
|
+
fs.writeFileSync(filePath, `${text}${section}\n`);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function updateSliceJson(filePath, completedAt) {
|
|
636
|
+
const json = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
637
|
+
json.status = 'completed';
|
|
638
|
+
json.completed_at = completedAt;
|
|
639
|
+
fs.writeFileSync(filePath, `${JSON.stringify(json, null, 2)}\n`);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function writeExecutionArtifacts(repoRoot, executorContext, details) {
|
|
643
|
+
const { slice } = executorContext;
|
|
644
|
+
const completedAt = details.completedAt || new Date().toISOString();
|
|
645
|
+
const artifacts = sliceLifecycleArtifactPaths(repoRoot, slice);
|
|
646
|
+
const changedFiles = uniqueList(details.changedFiles);
|
|
647
|
+
const closurePath = path.join(repoRoot, artifacts.closure);
|
|
648
|
+
const evidencePath = path.join(repoRoot, artifacts.evidence);
|
|
649
|
+
const commandLogPath = path.join(repoRoot, artifacts.commandLog);
|
|
650
|
+
const statusPath = path.join(repoRoot, artifacts.status);
|
|
651
|
+
const validationResults = Array.isArray(details.validationResults) ? details.validationResults : [];
|
|
652
|
+
const providerStdout = truncateText(redactSecrets(details.providerOutput?.stdout || ''), 1200).text;
|
|
653
|
+
const providerStderr = truncateText(redactSecrets(details.providerOutput?.stderr || ''), 1200).text;
|
|
654
|
+
|
|
655
|
+
fs.mkdirSync(path.dirname(closurePath), { recursive: true });
|
|
656
|
+
fs.writeFileSync(closurePath, renderClosureBrief({
|
|
657
|
+
slice,
|
|
658
|
+
changedFiles,
|
|
659
|
+
validationResults,
|
|
660
|
+
completedAt,
|
|
661
|
+
}));
|
|
662
|
+
|
|
663
|
+
const validationLines = validationResults.length > 0
|
|
664
|
+
? validationResults.map((result) => `- \`${result.command}\` -> exit ${result.exitCode}`)
|
|
665
|
+
: ['- No validation commands declared.'];
|
|
666
|
+
appendSection(evidencePath, `# Evidence Report - ${slice.specSlug}`, [
|
|
667
|
+
`## ${slice.sliceId} - Execution Evidence`,
|
|
668
|
+
'',
|
|
669
|
+
`- Completed at: ${completedAt}`,
|
|
670
|
+
`- Changed files: ${changedFiles.length}`,
|
|
671
|
+
...changedFiles.map((file) => ` - \`${file}\``),
|
|
672
|
+
`- Scope validation: passed`,
|
|
673
|
+
`- Provider stdout redacted: ${providerStdout ? 'yes' : 'n/a'}`,
|
|
674
|
+
`- Provider stderr redacted: ${providerStderr ? 'yes' : 'n/a'}`,
|
|
675
|
+
'',
|
|
676
|
+
'### Validation',
|
|
677
|
+
'',
|
|
678
|
+
...validationLines,
|
|
679
|
+
'',
|
|
680
|
+
'### Provider Output',
|
|
681
|
+
'',
|
|
682
|
+
'```text',
|
|
683
|
+
providerStdout || 'n/a',
|
|
684
|
+
providerStderr ? `\n${providerStderr}` : '',
|
|
685
|
+
'```',
|
|
686
|
+
].join('\n'));
|
|
687
|
+
|
|
688
|
+
const commandLogRows = [
|
|
689
|
+
`| ${completedAt} | ${slice.sliceId} | \`npx create-quiver ai execute-slice --slice ${slice.sliceRel}\` | passed |`,
|
|
690
|
+
...validationResults.map((result) => `| ${completedAt} | ${slice.sliceId} | \`${result.command}\` | exit ${result.exitCode} |`),
|
|
691
|
+
];
|
|
692
|
+
const commandLogHeader = [
|
|
693
|
+
'# Command Log',
|
|
694
|
+
'',
|
|
695
|
+
'| Timestamp | Slice | Command | Result |',
|
|
696
|
+
'|---|---|---|---|',
|
|
697
|
+
].join('\n');
|
|
698
|
+
const currentCommandLog = fs.existsSync(commandLogPath) ? fs.readFileSync(commandLogPath, 'utf8').trimEnd() : commandLogHeader;
|
|
699
|
+
fs.mkdirSync(path.dirname(commandLogPath), { recursive: true });
|
|
700
|
+
fs.writeFileSync(commandLogPath, `${currentCommandLog}\n${commandLogRows.join('\n')}\n`);
|
|
701
|
+
|
|
702
|
+
updateStatusMarkdown(statusPath, slice, completedAt);
|
|
703
|
+
updateSliceJson(path.join(repoRoot, artifacts.sliceJson), completedAt);
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
completedAt,
|
|
707
|
+
files: Object.values(artifacts),
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
|
|
495
711
|
async function runExecuteSlice(repoRoot, options = {}) {
|
|
712
|
+
const canonicalRepoRoot = canonicalizeRepoRoot(repoRoot);
|
|
496
713
|
const role = normalizeRole(options.role || DEFAULT_EXECUTE_ROLE);
|
|
497
714
|
const provider = options.providerExplicit === true || (options.provider && options.providerExplicit !== false)
|
|
498
715
|
? String(options.provider || DEFAULT_EXECUTE_PROVIDER).trim().toLowerCase()
|
|
499
|
-
: resolveProfileProvider(
|
|
716
|
+
: resolveProfileProvider(canonicalRepoRoot, role, DEFAULT_EXECUTE_PROVIDER);
|
|
500
717
|
const context = options.context || DEFAULT_EXECUTE_CONTEXT;
|
|
501
718
|
const timeoutMs = normalizeTimeout(options.timeout);
|
|
502
719
|
|
|
@@ -505,7 +722,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
505
722
|
}
|
|
506
723
|
|
|
507
724
|
const executorContext = buildExecuteSliceContext({
|
|
508
|
-
repoRoot,
|
|
725
|
+
repoRoot: canonicalRepoRoot,
|
|
509
726
|
slicePath: options.slice,
|
|
510
727
|
role,
|
|
511
728
|
context,
|
|
@@ -517,7 +734,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
517
734
|
try {
|
|
518
735
|
invocation = buildProviderInvocation(provider, {
|
|
519
736
|
prompt,
|
|
520
|
-
cwd:
|
|
737
|
+
cwd: canonicalRepoRoot,
|
|
521
738
|
timeoutMs,
|
|
522
739
|
});
|
|
523
740
|
} catch (error) {
|
|
@@ -551,7 +768,16 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
551
768
|
return report;
|
|
552
769
|
}
|
|
553
770
|
|
|
554
|
-
|
|
771
|
+
try {
|
|
772
|
+
assertCorrectSliceWorktree(canonicalRepoRoot, executorContext.slice, options);
|
|
773
|
+
} catch (error) {
|
|
774
|
+
throw appendRecovery(error, executorContext.slice);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const beforeSnapshot = captureWorktreeSnapshot(canonicalRepoRoot);
|
|
778
|
+
if (beforeSnapshot.files.length > 0 && options.commit === true) {
|
|
779
|
+
throw appendRecovery(new Error(formatError(`ai execute-slice --commit requires a clean worktree before running. Commit or stash first: ${beforeSnapshot.files.join(', ')}`)), executorContext.slice);
|
|
780
|
+
}
|
|
555
781
|
if (beforeSnapshot.files.length > 0 && options.allowDirty !== true) {
|
|
556
782
|
throw appendRecovery(new Error(formatError(`ai execute-slice requires a clean worktree before running. Commit or stash first: ${beforeSnapshot.files.join(', ')}`)), executorContext.slice);
|
|
557
783
|
}
|
|
@@ -560,7 +786,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
560
786
|
try {
|
|
561
787
|
result = await (options.runProviderFn || runProvider)(provider, {
|
|
562
788
|
prompt,
|
|
563
|
-
cwd:
|
|
789
|
+
cwd: canonicalRepoRoot,
|
|
564
790
|
timeoutMs,
|
|
565
791
|
dryRun: false,
|
|
566
792
|
probe: options.probe,
|
|
@@ -574,17 +800,27 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
574
800
|
}
|
|
575
801
|
|
|
576
802
|
if (result.stdout) {
|
|
577
|
-
process.stdout.write(result.stdout);
|
|
803
|
+
process.stdout.write(redactSecrets(result.stdout));
|
|
578
804
|
}
|
|
579
805
|
if (result.stderr) {
|
|
580
|
-
process.stderr.write(result.stderr);
|
|
806
|
+
process.stderr.write(redactSecrets(result.stderr));
|
|
581
807
|
}
|
|
582
808
|
|
|
583
809
|
if (!result.ok) {
|
|
584
810
|
throw appendRecovery(annotateProviderError(result.error || new Error('provider run failed'), 'execute-slice'), executorContext.slice);
|
|
585
811
|
}
|
|
586
812
|
|
|
587
|
-
const
|
|
813
|
+
const providerOutput = {
|
|
814
|
+
stdout: redactSecrets(result.stdout || ''),
|
|
815
|
+
stderr: redactSecrets(result.stderr || ''),
|
|
816
|
+
};
|
|
817
|
+
const sanitizedResult = {
|
|
818
|
+
...result,
|
|
819
|
+
stdout: providerOutput.stdout,
|
|
820
|
+
stderr: providerOutput.stderr,
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
const afterSnapshot = captureWorktreeSnapshot(canonicalRepoRoot);
|
|
588
824
|
let scopeResult;
|
|
589
825
|
try {
|
|
590
826
|
scopeResult = validateScopeSnapshot({
|
|
@@ -596,11 +832,16 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
596
832
|
} catch (error) {
|
|
597
833
|
throw appendRecovery(error, executorContext.slice);
|
|
598
834
|
}
|
|
835
|
+
if (scopeResult.changedFiles.length === 0) {
|
|
836
|
+
const error = new Error(formatError('provider produced no changed files; slice closure was not updated.'));
|
|
837
|
+
error.code = 'NO_CHANGES_TO_CLOSE';
|
|
838
|
+
throw appendRecovery(error, executorContext.slice);
|
|
839
|
+
}
|
|
599
840
|
|
|
600
841
|
let validationResults = [];
|
|
601
842
|
try {
|
|
602
843
|
validationResults = runValidationCommands(
|
|
603
|
-
|
|
844
|
+
canonicalRepoRoot,
|
|
604
845
|
executorContext.validationCommands,
|
|
605
846
|
options.runValidationCommandFn,
|
|
606
847
|
);
|
|
@@ -608,11 +849,18 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
608
849
|
throw appendRecovery(error, executorContext.slice);
|
|
609
850
|
}
|
|
610
851
|
|
|
611
|
-
const
|
|
852
|
+
const artifacts = writeExecutionArtifacts(canonicalRepoRoot, executorContext, {
|
|
853
|
+
changedFiles: scopeResult.changedFiles,
|
|
854
|
+
completedAt: new Date().toISOString(),
|
|
855
|
+
providerOutput,
|
|
856
|
+
validationResults,
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
const finalSnapshot = captureWorktreeSnapshot(canonicalRepoRoot);
|
|
612
860
|
let finalScopeResult;
|
|
613
861
|
try {
|
|
614
862
|
finalScopeResult = validateScopeSnapshot({
|
|
615
|
-
allowedFiles: executorContext.allowedFiles,
|
|
863
|
+
allowedFiles: uniqueList([...executorContext.allowedFiles, ...artifacts.files]),
|
|
616
864
|
beforeSnapshot,
|
|
617
865
|
afterSnapshot: finalSnapshot,
|
|
618
866
|
strict: true,
|
|
@@ -624,7 +872,7 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
624
872
|
let commitResult = null;
|
|
625
873
|
if (options.commit === true) {
|
|
626
874
|
try {
|
|
627
|
-
commitResult = commitSliceChanges(
|
|
875
|
+
commitResult = commitSliceChanges(canonicalRepoRoot, executorContext.slice, finalScopeResult.changedFiles, {
|
|
628
876
|
message: options.commitMessage,
|
|
629
877
|
});
|
|
630
878
|
} catch (error) {
|
|
@@ -649,12 +897,13 @@ async function runExecuteSlice(repoRoot, options = {}) {
|
|
|
649
897
|
slice: executorContext.slice.sliceId,
|
|
650
898
|
specSlug: executorContext.slice.specSlug,
|
|
651
899
|
invocation,
|
|
652
|
-
result,
|
|
900
|
+
result: sanitizedResult,
|
|
653
901
|
beforeSnapshot,
|
|
654
902
|
afterSnapshot: finalSnapshot,
|
|
655
903
|
scopeResult: finalScopeResult,
|
|
656
904
|
validationResults,
|
|
657
905
|
commitResult,
|
|
906
|
+
artifacts,
|
|
658
907
|
};
|
|
659
908
|
}
|
|
660
909
|
|
|
@@ -672,6 +921,8 @@ module.exports = {
|
|
|
672
921
|
commitSliceChanges,
|
|
673
922
|
formatExecuteSliceDryRunReport,
|
|
674
923
|
formatExecuteSliceResult,
|
|
924
|
+
assertCorrectSliceWorktree,
|
|
925
|
+
writeExecutionArtifacts,
|
|
675
926
|
runValidationCommand,
|
|
676
927
|
runValidationCommands,
|
|
677
928
|
normalizeTimeout,
|