create-quiver 0.12.1 → 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 +8 -0
- package/README.md +16 -8
- package/README_FOR_AI.md +11 -6
- package/ROADMAP.md +9 -2
- package/docs/COMMANDS.md.template +9 -2
- package/package.json +2 -1
- package/specs/quiver-v26-0121-smoke-hardening/SPEC.md +2 -2
- package/specs/quiver-v26-0121-smoke-hardening/STATUS.md +5 -5
- 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 +84 -9
- package/src/create-quiver/commands/flow.js +52 -4
- package/src/create-quiver/commands/graph.js +7 -7
- package/src/create-quiver/commands/plan.js +6 -15
- package/src/create-quiver/commands/spec.js +282 -0
- package/src/create-quiver/index.js +83 -21
- package/src/create-quiver/lib/agent-profiles.js +15 -3
- package/src/create-quiver/lib/ai/artifacts.js +318 -0
- package/src/create-quiver/lib/ai/execution-plan.js +9 -0
- package/src/create-quiver/lib/ai/executor.js +3 -2
- package/src/create-quiver/lib/ai/export-state.js +242 -97
- package/src/create-quiver/lib/ai/github.js +80 -3
- package/src/create-quiver/lib/ai/plan-review.js +2 -0
- package/src/create-quiver/lib/ai/spec-generator.js +72 -13
- package/src/create-quiver/lib/ai/spec-templates.js +72 -12
- package/src/create-quiver/lib/analyze.js +2 -2
- package/src/create-quiver/lib/approvals.js +14 -2
- package/src/create-quiver/lib/doctor.js +79 -0
- package/src/create-quiver/lib/git.js +40 -1
- package/src/create-quiver/lib/handoff.js +43 -1
- package/src/create-quiver/lib/init-docs.js +11 -7
- package/src/create-quiver/lib/init-layout.js +1 -0
- 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 +48 -7
- package/src/create-quiver/lib/scope.js +2 -1
- package/src/create-quiver/lib/slice.js +8 -4
- package/src/create-quiver/lib/spec-worktrees.js +121 -38
- package/src/create-quiver/lib/statuses.js +115 -0
|
@@ -39,9 +39,14 @@ const { runFlow } = require('./commands/flow');
|
|
|
39
39
|
const { runGraph } = require('./commands/graph');
|
|
40
40
|
const { runNext } = require('./commands/next');
|
|
41
41
|
const { runPlan } = require('./commands/plan');
|
|
42
|
-
const { runCreateSpec } = require('./commands/spec');
|
|
42
|
+
const { runCreateSpec, runValidateSpec } = require('./commands/spec');
|
|
43
43
|
const { buildInitLayout, formatInitLayoutPlan } = require('./lib/init-layout');
|
|
44
|
-
const {
|
|
44
|
+
const {
|
|
45
|
+
formatInstallSelfCommand,
|
|
46
|
+
initializeProjectDocs,
|
|
47
|
+
installSelfAsDevDep,
|
|
48
|
+
refreshAiContextDoc,
|
|
49
|
+
} = require('./lib/init-docs');
|
|
45
50
|
const { checkPrReadiness, checkScope, checkSliceReadiness } = require('./lib/readiness');
|
|
46
51
|
const { cleanupSlice, refreshActiveSlicesBoard, startSlice } = require('./lib/lifecycle');
|
|
47
52
|
const { buildSpecStatus, closeSpecWorktree, formatSpecCloseResult, formatSpecStartResult, formatSpecStatus, startSpecWorktree } = require('./lib/spec-worktrees');
|
|
@@ -119,7 +124,7 @@ const SUPPORTED_AI_COMMANDS = new Set([
|
|
|
119
124
|
'trace',
|
|
120
125
|
]);
|
|
121
126
|
|
|
122
|
-
const SUPPORTED_SPEC_COMMANDS = new Set(['close', 'create', 'start', 'status']);
|
|
127
|
+
const SUPPORTED_SPEC_COMMANDS = new Set(['close', 'create', 'start', 'status', 'validate']);
|
|
123
128
|
const SUPPORTED_DEMO_COMMANDS = new Set(['create']);
|
|
124
129
|
|
|
125
130
|
function unsupportedCommandMessage(commandName) {
|
|
@@ -159,7 +164,7 @@ const COMMAND_HELP_GROUPS = [
|
|
|
159
164
|
['ai resume', 'Resume guidance from the last valid lifecycle phase without chat memory.'],
|
|
160
165
|
['ai onboard', 'Run or print the planner onboarding prompt with a token-aware context pack.'],
|
|
161
166
|
['ai prepare-context', 'Preview or write docs-only AI context updates with assumptions and risks.'],
|
|
162
|
-
['ai agent set|list|show', 'Manage planner, executor, reviewer, and doctor provider profiles without secrets.'],
|
|
167
|
+
['ai agent set|list|show', 'Manage planner, executor, reviewer, and doctor provider profiles without secrets; use set --dry-run to preview.'],
|
|
163
168
|
['ai plan', 'Generate versioned planner drafts for acceptance criteria, technical plan, or spec phase.'],
|
|
164
169
|
['ai revise', 'Create a new planner draft from human feedback without approving it.'],
|
|
165
170
|
['ai review-plan', 'Review the technical-plan draft for production readiness before approval.'],
|
|
@@ -188,6 +193,7 @@ const COMMAND_HELP_GROUPS = [
|
|
|
188
193
|
['spec create', 'Create the real spec tree from a reviewed approved technical plan.'],
|
|
189
194
|
['spec start', 'Create or reuse the dedicated worktree and branch for one spec.'],
|
|
190
195
|
['spec status', 'Show spec worktree, branch, slice-00 state, and pending slices.'],
|
|
196
|
+
['spec validate', 'Validate spec docs, slices, briefs, evidence, status, dependencies, and safe paths.'],
|
|
191
197
|
['spec close', 'Close a merged clean spec worktree and guide local sync.'],
|
|
192
198
|
['start-slice', 'Start work on one slice and mark it active.'],
|
|
193
199
|
['check-slice', 'Validate slice structure, dependencies, scope, and readiness.'],
|
|
@@ -263,6 +269,7 @@ function printUsage() {
|
|
|
263
269
|
npx create-quiver spec create [options]
|
|
264
270
|
npx create-quiver spec start <spec-dir>
|
|
265
271
|
npx create-quiver spec status <spec-dir>
|
|
272
|
+
npx create-quiver spec validate <spec-dir>
|
|
266
273
|
npx create-quiver spec close <spec-dir>
|
|
267
274
|
npx create-quiver evidence run [options] -- <command>
|
|
268
275
|
npx create-quiver demo create spec-viewer [options]
|
|
@@ -282,12 +289,13 @@ Options:
|
|
|
282
289
|
--all-ready List every ready slice returned by next
|
|
283
290
|
--auto-start Prompt for confirmation and run start-slice on next
|
|
284
291
|
--local For check-slice, run structural validation without remote/base checks
|
|
292
|
+
--strict Treat supported validation warnings as failures
|
|
285
293
|
--unicode Prefer Unicode output when supported
|
|
286
294
|
--minimal Plan or run the minimal init profile
|
|
287
295
|
--full Plan or run the full compatibility init profile
|
|
288
296
|
--legacy-scripts Include legacy Bash wrappers in init profile
|
|
289
297
|
--include-templates Export packaged templates in init profile
|
|
290
|
-
--dry-run Preview init, migrate, prepare, spec create/start/close, demo, or AI work without executing writes/providers
|
|
298
|
+
--dry-run Preview init, analyze, migrate, prepare, spec create/start/close, demo, ai agent set, or AI work without executing writes/providers
|
|
291
299
|
--print-prompt Print the exact AI prompt and exit without executing provider CLIs
|
|
292
300
|
--fix For doctor, apply safe non-destructive repairs
|
|
293
301
|
--execute For ai execute-plan, run the planned slices instead of printing commands
|
|
@@ -302,7 +310,7 @@ Options:
|
|
|
302
310
|
--ssh-host-alias <name> SSH host alias to validate for prepare or AI commands
|
|
303
311
|
--identity-file <path> SSH identity file to validate for prepare or AI commands
|
|
304
312
|
--remote <name> Git remote name for check-slice or AI PR checks
|
|
305
|
-
--base <branch> Base branch for check-slice, ai pr, or spec close (default: main)
|
|
313
|
+
--base <branch> Base branch for check-slice, check-scope, ai pr, or spec close (default: main)
|
|
306
314
|
--output <file> Output file for evidence run
|
|
307
315
|
--max-output <n> Maximum stdout/stderr chars per evidence section
|
|
308
316
|
--title <text> Override PR title for ai pr create
|
|
@@ -330,6 +338,7 @@ Examples:
|
|
|
330
338
|
cd ./my-project && npx create-quiver ai specs list
|
|
331
339
|
cd ./my-project && npx create-quiver ai slices list --json
|
|
332
340
|
cd ./my-project && npx create-quiver ai trace report
|
|
341
|
+
cd ./my-project && npx create-quiver ai agent set planner --provider codex --model gpt-5.5 --dry-run
|
|
333
342
|
cd ./my-project && npx create-quiver ai agent set planner --provider codex --model gpt-5.5
|
|
334
343
|
cd ./my-project && npx create-quiver ai agent list
|
|
335
344
|
cd ./my-project && npx create-quiver ai plan --phase acceptance --input requirements.md --dry-run
|
|
@@ -368,6 +377,7 @@ Examples:
|
|
|
368
377
|
cd ./my-project && npx create-quiver refresh-active-slices
|
|
369
378
|
cd ./my-project && npx create-quiver spec start specs/my-project
|
|
370
379
|
cd ./my-project && npx create-quiver spec status specs/my-project
|
|
380
|
+
cd ./my-project && npx create-quiver spec validate specs/my-project
|
|
371
381
|
cd ./my-project && npx create-quiver spec close specs/my-project --dry-run
|
|
372
382
|
cd ./my-project && npx create-quiver evidence run -- npm test
|
|
373
383
|
cd ./my-project && npx create-quiver demo create spec-viewer --dry-run
|
|
@@ -951,7 +961,7 @@ function parseArgs(argv) {
|
|
|
951
961
|
result.specCommand = positional.shift();
|
|
952
962
|
}
|
|
953
963
|
if (!result.specCommand) {
|
|
954
|
-
throw new Error(formatError('missing spec subcommand. Use: npx create-quiver spec <create|start|status|close>'));
|
|
964
|
+
throw new Error(formatError('missing spec subcommand. Use: npx create-quiver spec <create|start|status|validate|close>'));
|
|
955
965
|
}
|
|
956
966
|
if (result.specCommand !== 'create' && positional.length > 0) {
|
|
957
967
|
result.targetDir = positional.shift();
|
|
@@ -1574,8 +1584,8 @@ function detectFrameworks(projectRoot, files, rootEntries, packageJson) {
|
|
|
1574
1584
|
},
|
|
1575
1585
|
{
|
|
1576
1586
|
name: 'vue',
|
|
1577
|
-
matches: () => dependencies.has('vue') || rootFileSet.has('vue.config.js')
|
|
1578
|
-
signals: ['vue', 'vue.config.*'
|
|
1587
|
+
matches: () => dependencies.has('vue') || rootFileSet.has('vue.config.js'),
|
|
1588
|
+
signals: ['vue', 'vue.config.*'],
|
|
1579
1589
|
},
|
|
1580
1590
|
{
|
|
1581
1591
|
name: 'react',
|
|
@@ -1998,7 +2008,7 @@ function writeProjectScanArtifacts(projectRoot, scan) {
|
|
|
1998
2008
|
return { jsonPath, mdPath: scanPaths.projectMapPath };
|
|
1999
2009
|
}
|
|
2000
2010
|
|
|
2001
|
-
function runAnalyze(targetDir) {
|
|
2011
|
+
function runAnalyze(targetDir, options = {}) {
|
|
2002
2012
|
const projectRoot = resolveTargetRoot(process.cwd(), targetDir);
|
|
2003
2013
|
|
|
2004
2014
|
if (!fs.existsSync(projectRoot)) {
|
|
@@ -2006,6 +2016,26 @@ function runAnalyze(targetDir) {
|
|
|
2006
2016
|
}
|
|
2007
2017
|
|
|
2008
2018
|
const scan = buildProjectScan(projectRoot);
|
|
2019
|
+
|
|
2020
|
+
if (options.dryRun) {
|
|
2021
|
+
console.log(`Project analysis dry-run for ${projectRoot}`);
|
|
2022
|
+
console.log('Writes: none');
|
|
2023
|
+
console.log(`Would write ${CURRENT_SCAN_RELATIVE_PATH}`);
|
|
2024
|
+
console.log(`Would write ${PROJECT_MAP_RELATIVE_PATH}`);
|
|
2025
|
+
console.log('Would refresh docs/AI_CONTEXT.md');
|
|
2026
|
+
console.log(`Detected primary stack: ${scan.stack.primary}`);
|
|
2027
|
+
console.log(`Detected frameworks: ${scan.stack.frameworks.length > 0 ? scan.stack.frameworks.join(', ') : 'none detected'}`);
|
|
2028
|
+
console.log(`Detected package manager: ${scan.project.package_manager}`);
|
|
2029
|
+
return {
|
|
2030
|
+
artifacts: {
|
|
2031
|
+
jsonPath: path.join(projectRoot, CURRENT_SCAN_RELATIVE_PATH),
|
|
2032
|
+
mdPath: path.join(projectRoot, PROJECT_MAP_RELATIVE_PATH),
|
|
2033
|
+
},
|
|
2034
|
+
dryRun: true,
|
|
2035
|
+
scan,
|
|
2036
|
+
};
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2009
2039
|
const artifacts = writeProjectScanArtifacts(projectRoot, scan);
|
|
2010
2040
|
const aiContextPath = refreshAiContextDoc(projectRoot, scan);
|
|
2011
2041
|
updateStateForAnalyze(projectRoot, CLI_VERSION);
|
|
@@ -2016,6 +2046,13 @@ function runAnalyze(targetDir) {
|
|
|
2016
2046
|
console.log(`Wrote ${relativePosixPath(projectRoot, aiContextPath)}`);
|
|
2017
2047
|
console.log(`Detected primary stack: ${scan.stack.primary}`);
|
|
2018
2048
|
console.log(`Detected package manager: ${scan.project.package_manager}`);
|
|
2049
|
+
|
|
2050
|
+
return {
|
|
2051
|
+
artifacts,
|
|
2052
|
+
aiContextPath,
|
|
2053
|
+
dryRun: false,
|
|
2054
|
+
scan,
|
|
2055
|
+
};
|
|
2019
2056
|
}
|
|
2020
2057
|
|
|
2021
2058
|
function runMigrate(targetDir, options = {}) {
|
|
@@ -2079,7 +2116,7 @@ function runMigrate(targetDir, options = {}) {
|
|
|
2079
2116
|
if (installResult === 'installed') {
|
|
2080
2117
|
console.log(`Added create-quiver@${CLI_VERSION} as dev dependency`);
|
|
2081
2118
|
} else if (installResult === 'failed') {
|
|
2082
|
-
console.warn(`Warning: could not install create-quiver automatically. Run:
|
|
2119
|
+
console.warn(`Warning: could not install create-quiver automatically. Run: ${formatInstallSelfCommand(projectRoot, CLI_VERSION)}`);
|
|
2083
2120
|
}
|
|
2084
2121
|
}
|
|
2085
2122
|
|
|
@@ -2117,6 +2154,11 @@ function runDoctor(targetDir, options = {}) {
|
|
|
2117
2154
|
|
|
2118
2155
|
const doctorReport = collectDoctorReport(projectRoot);
|
|
2119
2156
|
const specSlugs = doctorReport.specSlugs;
|
|
2157
|
+
const doctorExampleTarget = doctorReport.exampleTarget || {
|
|
2158
|
+
sliceId: '<slice-id>',
|
|
2159
|
+
source: 'generic',
|
|
2160
|
+
specSlug: '<spec-slug>',
|
|
2161
|
+
};
|
|
2120
2162
|
const specRequiredFiles = specSlugs.flatMap((projectSlug) => [
|
|
2121
2163
|
`specs/${projectSlug}/SPEC.md`,
|
|
2122
2164
|
`specs/${projectSlug}/STATUS.md`,
|
|
@@ -2237,16 +2279,22 @@ function runDoctor(targetDir, options = {}) {
|
|
|
2237
2279
|
if (!hasQuiverState) {
|
|
2238
2280
|
console.log('- Run migration first: npx create-quiver migrate');
|
|
2239
2281
|
} else if (!hasScanArtifacts) {
|
|
2240
|
-
|
|
2282
|
+
console.log('- Analyze the project first: npx create-quiver analyze');
|
|
2241
2283
|
} else {
|
|
2242
2284
|
console.log('- Ask your AI agent: Read AGENTS.md, then docs/AI_ONBOARDING_PROMPT.md and execute it.');
|
|
2243
2285
|
}
|
|
2244
2286
|
console.log('- Check the next ready slice: npx create-quiver next');
|
|
2245
2287
|
if (specSlugs.length > 0) {
|
|
2246
|
-
const projectSlug =
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2288
|
+
const projectSlug = doctorExampleTarget.specSlug;
|
|
2289
|
+
const sliceId = doctorExampleTarget.sliceId || '<slice-id>';
|
|
2290
|
+
if (doctorExampleTarget.source === 'active-slice') {
|
|
2291
|
+
console.log(`- Example target: ${projectSlug}/${sliceId} (${doctorExampleTarget.status})`);
|
|
2292
|
+
} else if (doctorExampleTarget.source === 'generic-multiple-specs') {
|
|
2293
|
+
console.log('- Example target: specs/<spec-slug>/slices/<slice-id>/slice.json (generic because no active slice is obvious)');
|
|
2294
|
+
}
|
|
2295
|
+
console.log(`- Start a slice: npx create-quiver start-slice specs/${projectSlug}/slices/${sliceId}/slice.json`);
|
|
2296
|
+
console.log(`- Validate a slice: npx create-quiver check-slice specs/${projectSlug}/slices/${sliceId}/slice.json`);
|
|
2297
|
+
console.log(`- Validate the PR gate: npx create-quiver check-pr specs/${projectSlug}/slices/${sliceId}/slice.json`);
|
|
2250
2298
|
} else {
|
|
2251
2299
|
console.log('- Create real specs and slices only after acceptance criteria are approved and the technical plan is reviewed and approved.');
|
|
2252
2300
|
}
|
|
@@ -2280,7 +2328,9 @@ async function run(argv) {
|
|
|
2280
2328
|
}
|
|
2281
2329
|
|
|
2282
2330
|
if (args.mode === 'analyze') {
|
|
2283
|
-
runAnalyze(args.targetDir
|
|
2331
|
+
runAnalyze(args.targetDir, {
|
|
2332
|
+
dryRun: args.dryRun,
|
|
2333
|
+
});
|
|
2284
2334
|
return;
|
|
2285
2335
|
}
|
|
2286
2336
|
|
|
@@ -2387,6 +2437,7 @@ async function run(argv) {
|
|
|
2387
2437
|
model: args.aiModel || undefined,
|
|
2388
2438
|
provider: args.aiProviderExplicit ? args.aiProvider : undefined,
|
|
2389
2439
|
role: args.aiAgentRole || undefined,
|
|
2440
|
+
dryRun: args.dryRun,
|
|
2390
2441
|
});
|
|
2391
2442
|
return;
|
|
2392
2443
|
}
|
|
@@ -2655,7 +2706,11 @@ async function run(argv) {
|
|
|
2655
2706
|
}
|
|
2656
2707
|
|
|
2657
2708
|
if (args.mode === 'check-scope') {
|
|
2658
|
-
checkScope(args.targetDir, {
|
|
2709
|
+
checkScope(args.targetDir, {
|
|
2710
|
+
baseBranch: args.baseBranchExplicit ? args.aiBaseBranch : '',
|
|
2711
|
+
remote: args.aiRemote,
|
|
2712
|
+
strict: args.strict,
|
|
2713
|
+
});
|
|
2659
2714
|
return;
|
|
2660
2715
|
}
|
|
2661
2716
|
|
|
@@ -2676,7 +2731,7 @@ async function run(argv) {
|
|
|
2676
2731
|
}
|
|
2677
2732
|
|
|
2678
2733
|
if (!args.targetDir || args.targetDir === '.') {
|
|
2679
|
-
throw new Error(formatError('missing spec directory. Use: npx create-quiver spec <start|status|close> <spec-dir>'));
|
|
2734
|
+
throw new Error(formatError('missing spec directory. Use: npx create-quiver spec <start|status|validate|close> <spec-dir>'));
|
|
2680
2735
|
}
|
|
2681
2736
|
|
|
2682
2737
|
if (args.specCommand === 'start') {
|
|
@@ -2693,6 +2748,13 @@ async function run(argv) {
|
|
|
2693
2748
|
return;
|
|
2694
2749
|
}
|
|
2695
2750
|
|
|
2751
|
+
if (args.specCommand === 'validate') {
|
|
2752
|
+
runValidateSpec(process.cwd(), args.targetDir, {
|
|
2753
|
+
strict: args.strict,
|
|
2754
|
+
});
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2696
2758
|
if (args.specCommand === 'close') {
|
|
2697
2759
|
const report = closeSpecWorktree(process.cwd(), args.targetDir, {
|
|
2698
2760
|
baseBranch: args.aiBaseBranch,
|
|
@@ -2705,7 +2767,7 @@ async function run(argv) {
|
|
|
2705
2767
|
return;
|
|
2706
2768
|
}
|
|
2707
2769
|
|
|
2708
|
-
throw new Error(formatError(`unsupported spec subcommand: ${args.specCommand}. Supported tasks: create, start, status, close`));
|
|
2770
|
+
throw new Error(formatError(`unsupported spec subcommand: ${args.specCommand}. Supported tasks: create, start, status, validate, close`));
|
|
2709
2771
|
}
|
|
2710
2772
|
|
|
2711
2773
|
const packageRoot = path.resolve(__dirname, '../..');
|
|
@@ -2748,7 +2810,7 @@ async function run(argv) {
|
|
|
2748
2810
|
if (installResult === 'installed') {
|
|
2749
2811
|
console.log(`Added create-quiver@${CLI_VERSION} as dev dependency`);
|
|
2750
2812
|
} else if (installResult === 'failed') {
|
|
2751
|
-
console.warn(`Warning: could not install create-quiver automatically. Run:
|
|
2813
|
+
console.warn(`Warning: could not install create-quiver automatically. Run: ${formatInstallSelfCommand(targetDir, CLI_VERSION)}`);
|
|
2752
2814
|
}
|
|
2753
2815
|
}
|
|
2754
2816
|
|
|
@@ -95,6 +95,16 @@ function listAgentProfiles(projectRoot) {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
function setAgentProfile(projectRoot, role, options = {}) {
|
|
98
|
+
const next = buildAgentProfileState(projectRoot, role, options);
|
|
99
|
+
|
|
100
|
+
const filePath = writeAgentProfiles(projectRoot, next.state);
|
|
101
|
+
return {
|
|
102
|
+
filePath,
|
|
103
|
+
profile: next.profile,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildAgentProfileState(projectRoot, role, options = {}) {
|
|
98
108
|
const normalizedRole = normalizeAgentProfileRole(role);
|
|
99
109
|
const provider = assertSupportedProvider(options.provider);
|
|
100
110
|
const model = normalizeOptionalText(options.model, 'model');
|
|
@@ -102,7 +112,7 @@ function setAgentProfile(projectRoot, role, options = {}) {
|
|
|
102
112
|
const context = normalizeOptionalText(options.context, 'context');
|
|
103
113
|
const state = readAgentProfiles(projectRoot);
|
|
104
114
|
const current = state.profiles[normalizedRole] || {};
|
|
105
|
-
const now = new Date().toISOString();
|
|
115
|
+
const now = options.now instanceof Date ? options.now.toISOString() : new Date().toISOString();
|
|
106
116
|
const profile = {
|
|
107
117
|
role: normalizedRole,
|
|
108
118
|
provider,
|
|
@@ -119,10 +129,11 @@ function setAgentProfile(projectRoot, role, options = {}) {
|
|
|
119
129
|
};
|
|
120
130
|
state.updated_at = now;
|
|
121
131
|
|
|
122
|
-
const filePath = writeAgentProfiles(projectRoot, state);
|
|
123
132
|
return {
|
|
124
|
-
|
|
133
|
+
action: current.provider ? 'update' : 'create',
|
|
134
|
+
filePath: agentProfilesPath(projectRoot),
|
|
125
135
|
profile,
|
|
136
|
+
state,
|
|
126
137
|
};
|
|
127
138
|
}
|
|
128
139
|
|
|
@@ -139,6 +150,7 @@ module.exports = {
|
|
|
139
150
|
PROFILE_STATE_VERSION,
|
|
140
151
|
agentProfilesPath,
|
|
141
152
|
formatProviderList,
|
|
153
|
+
buildAgentProfileState,
|
|
142
154
|
getAgentProfile,
|
|
143
155
|
listAgentProfiles,
|
|
144
156
|
normalizeAgentProfileRole,
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const os = require('node:os');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const { redactSecrets } = require('../evidence');
|
|
6
|
+
const { quiverInternalPaths } = require('../init-layout');
|
|
7
|
+
|
|
8
|
+
const RAW_ARTIFACT_SCHEMA_VERSION = 1;
|
|
9
|
+
const DEFAULT_MAX_PROVIDER_PROMPT_BYTES = 1024 * 1024;
|
|
10
|
+
const DEFAULT_MAX_REVISION_INPUT_BYTES = 400 * 1024;
|
|
11
|
+
const DEFAULT_COMPACTED_REVISION_INPUT_BYTES = 120 * 1024;
|
|
12
|
+
|
|
13
|
+
const IMPORTANT_REVISION_LINE = /\b(acceptance|criteri[ao]s?|decision|decisi[o\u00f3]n|risk|riesgo|file|archivo|changed|cambio|scope|alcance|validation|validaci[o\u00f3]n|test|blocker|bloque|dependency|dependencia|assumption|supuesto|pending|pendiente|error|rollback|evidence|evidencia)\b/i;
|
|
14
|
+
const PROVIDER_LOG_LINE = /^\s*(?:\[?(?:debug|info|notice|trace|warn|warning)\]?[:\s-]|(?:codex|claude|gemini)\b.*(?:provider|model|prompt|token|running|loading|thinking)|(?:using|loading)\s+(?:model|provider)\b|prompt\s+(?:length|transport)\s*:)/i;
|
|
15
|
+
|
|
16
|
+
function formatError(message) {
|
|
17
|
+
return `create-quiver: ${message}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function byteLength(value) {
|
|
21
|
+
return Buffer.byteLength(String(value || ''), 'utf8');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeText(value) {
|
|
25
|
+
return String(value || '')
|
|
26
|
+
.replace(/\r\n/g, '\n')
|
|
27
|
+
.replace(/\r/g, '\n')
|
|
28
|
+
.replace(/\u001b\[[0-9;]*m/g, '');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function escapeRegExp(value) {
|
|
32
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function redactSensitiveLocalValues(text, options = {}) {
|
|
36
|
+
let result = redactSecrets(text);
|
|
37
|
+
const replacements = [];
|
|
38
|
+
|
|
39
|
+
if (options.projectRoot) {
|
|
40
|
+
replacements.push({
|
|
41
|
+
value: path.resolve(options.projectRoot),
|
|
42
|
+
label: '[PROJECT_ROOT]',
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (os.homedir()) {
|
|
47
|
+
replacements.push({
|
|
48
|
+
value: os.homedir(),
|
|
49
|
+
label: '[HOME]',
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const replacement of replacements) {
|
|
54
|
+
if (!replacement.value) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
result = result.replace(new RegExp(escapeRegExp(replacement.value), 'g'), replacement.label);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizePositiveInteger(value, fallback) {
|
|
64
|
+
if (value === undefined || value === null || value === '') {
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
const parsed = Number(value);
|
|
68
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
69
|
+
return fallback;
|
|
70
|
+
}
|
|
71
|
+
return Math.floor(parsed);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function resolveAiArtifactLimits(options = {}) {
|
|
75
|
+
return {
|
|
76
|
+
maxProviderPromptBytes: normalizePositiveInteger(
|
|
77
|
+
options.maxProviderPromptBytes ?? process.env.QUIVER_AI_MAX_PROMPT_BYTES,
|
|
78
|
+
DEFAULT_MAX_PROVIDER_PROMPT_BYTES,
|
|
79
|
+
),
|
|
80
|
+
maxRevisionInputBytes: normalizePositiveInteger(
|
|
81
|
+
options.maxRevisionInputBytes ?? process.env.QUIVER_AI_MAX_REVISION_INPUT_BYTES,
|
|
82
|
+
DEFAULT_MAX_REVISION_INPUT_BYTES,
|
|
83
|
+
),
|
|
84
|
+
compactedRevisionInputBytes: normalizePositiveInteger(
|
|
85
|
+
options.compactedRevisionInputBytes ?? process.env.QUIVER_AI_COMPACTED_REVISION_INPUT_BYTES,
|
|
86
|
+
DEFAULT_COMPACTED_REVISION_INPUT_BYTES,
|
|
87
|
+
),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function stripPromptEcho(text, prompt) {
|
|
92
|
+
const normalizedText = normalizeText(text);
|
|
93
|
+
const normalizedPrompt = normalizeText(prompt).trim();
|
|
94
|
+
|
|
95
|
+
if (!normalizedPrompt || normalizedPrompt.length < 80) {
|
|
96
|
+
return normalizedText;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const directIndex = normalizedText.indexOf(normalizedPrompt);
|
|
100
|
+
if (directIndex >= 0) {
|
|
101
|
+
return `${normalizedText.slice(0, directIndex)}${normalizedText.slice(directIndex + normalizedPrompt.length)}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return normalizedText;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function stripProviderLogEdges(text) {
|
|
108
|
+
const lines = normalizeText(text).split('\n');
|
|
109
|
+
|
|
110
|
+
while (lines.length > 0 && (PROVIDER_LOG_LINE.test(lines[0]) || lines[0].trim() === '')) {
|
|
111
|
+
lines.shift();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
while (lines.length > 0 && (PROVIDER_LOG_LINE.test(lines[lines.length - 1]) || lines[lines.length - 1].trim() === '')) {
|
|
115
|
+
lines.pop();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return lines.join('\n').trim();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeDraftOutput(text, sourceText = text) {
|
|
122
|
+
const value = normalizeText(text).trim();
|
|
123
|
+
if (!value) {
|
|
124
|
+
return '';
|
|
125
|
+
}
|
|
126
|
+
return /\n\s*$/.test(String(sourceText || '')) ? `${value}\n` : value;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function extractCleanProviderOutput(result, options = {}) {
|
|
130
|
+
const stdout = redactSensitiveLocalValues(result?.stdout || '', options);
|
|
131
|
+
const stderr = redactSensitiveLocalValues(result?.stderr || '', options);
|
|
132
|
+
const primary = stdout.trim() ? stdout : stderr;
|
|
133
|
+
const cleaned = stripProviderLogEdges(stripPromptEcho(primary, options.prompt || ''));
|
|
134
|
+
const cleanOutput = normalizeDraftOutput(cleaned, primary);
|
|
135
|
+
|
|
136
|
+
if (cleanOutput) {
|
|
137
|
+
return {
|
|
138
|
+
cleanOutput,
|
|
139
|
+
source: stdout.trim() ? 'stdout' : 'stderr',
|
|
140
|
+
strippedPromptEcho: primary !== stripPromptEcho(primary, options.prompt || ''),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
cleanOutput: normalizeDraftOutput(primary || [stdout, stderr].filter(Boolean).join('\n'), primary),
|
|
146
|
+
source: stdout.trim() ? 'stdout' : stderr.trim() ? 'stderr' : 'empty',
|
|
147
|
+
strippedPromptEcho: false,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function safeArtifactName(scope, now = new Date()) {
|
|
152
|
+
const slug = String(scope || 'provider-output')
|
|
153
|
+
.trim()
|
|
154
|
+
.toLowerCase()
|
|
155
|
+
.replace(/[^a-z0-9._-]+/g, '-')
|
|
156
|
+
.replace(/^-+|-+$/g, '') || 'provider-output';
|
|
157
|
+
const stamp = now.toISOString()
|
|
158
|
+
.replace(/\.\d{3}Z$/, 'z')
|
|
159
|
+
.replace(/[^0-9a-z]+/gi, '-')
|
|
160
|
+
.toLowerCase()
|
|
161
|
+
.replace(/^-+|-+$/g, '');
|
|
162
|
+
return `${stamp}-${slug}.json`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function toRelativePosix(root, filePath) {
|
|
166
|
+
return path.relative(root, filePath).split(path.sep).join('/');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function writeRawProviderArtifact(projectRoot, runId, scope, result, options = {}) {
|
|
170
|
+
if (!runId) {
|
|
171
|
+
throw new Error(formatError('missing AI run id for raw provider artifact'));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const now = options.now || new Date();
|
|
175
|
+
const rawDir = path.join(quiverInternalPaths(projectRoot).runsDir, String(runId), 'raw');
|
|
176
|
+
const rawPath = path.join(rawDir, safeArtifactName(scope, now));
|
|
177
|
+
const serializedError = result?.error
|
|
178
|
+
? {
|
|
179
|
+
code: result.error.code || null,
|
|
180
|
+
message: result.error.message || String(result.error),
|
|
181
|
+
provider: result.error.provider || null,
|
|
182
|
+
command: result.error.command || null,
|
|
183
|
+
}
|
|
184
|
+
: null;
|
|
185
|
+
const artifact = {
|
|
186
|
+
schema_version: RAW_ARTIFACT_SCHEMA_VERSION,
|
|
187
|
+
kind: 'provider-output',
|
|
188
|
+
scope: String(scope || 'provider-output'),
|
|
189
|
+
created_at: now.toISOString(),
|
|
190
|
+
provider: result?.provider || null,
|
|
191
|
+
command: result?.command || null,
|
|
192
|
+
args: Array.isArray(result?.args) ? result.args.slice() : [],
|
|
193
|
+
cwd: result?.cwd ? redactSensitiveLocalValues(result.cwd, { projectRoot }) : null,
|
|
194
|
+
ok: Boolean(result?.ok),
|
|
195
|
+
dry_run: Boolean(result?.dryRun),
|
|
196
|
+
exit_code: typeof result?.exitCode === 'number' ? result.exitCode : null,
|
|
197
|
+
signal: result?.signal || null,
|
|
198
|
+
timeout_ms: typeof result?.timeoutMs === 'number' ? result.timeoutMs : null,
|
|
199
|
+
prompt_transport: result?.promptTransport || null,
|
|
200
|
+
stdout: redactSensitiveLocalValues(result?.stdout || '', { projectRoot }),
|
|
201
|
+
stderr: redactSensitiveLocalValues(result?.stderr || '', { projectRoot }),
|
|
202
|
+
error: serializedError ? JSON.parse(redactSensitiveLocalValues(JSON.stringify(serializedError), { projectRoot })) : null,
|
|
203
|
+
metadata: options.metadata || {},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
fs.mkdirSync(rawDir, { recursive: true });
|
|
207
|
+
fs.writeFileSync(rawPath, `${JSON.stringify(artifact, null, 2)}\n`);
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
filePath: rawPath,
|
|
211
|
+
path: toRelativePosix(projectRoot, rawPath),
|
|
212
|
+
artifact,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function compactTextToByteLimit(text, maxBytes) {
|
|
217
|
+
let value = normalizeText(text).trim();
|
|
218
|
+
if (byteLength(value) <= maxBytes) {
|
|
219
|
+
return value;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
while (byteLength(value) > maxBytes && value.length > 0) {
|
|
223
|
+
value = value.slice(0, Math.max(0, value.length - Math.ceil((byteLength(value) - maxBytes) / 2) - 32)).trimEnd();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function compactRevisionInput(inputText, options = {}) {
|
|
230
|
+
const limits = resolveAiArtifactLimits(options);
|
|
231
|
+
const originalText = normalizeText(inputText);
|
|
232
|
+
const originalBytes = byteLength(originalText);
|
|
233
|
+
|
|
234
|
+
if (originalBytes <= limits.maxRevisionInputBytes) {
|
|
235
|
+
return {
|
|
236
|
+
text: originalText,
|
|
237
|
+
compaction: null,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const lines = originalText.split('\n');
|
|
242
|
+
const selected = [];
|
|
243
|
+
const seen = new Set();
|
|
244
|
+
const addLine = (line) => {
|
|
245
|
+
const key = line;
|
|
246
|
+
if (seen.has(key)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
seen.add(key);
|
|
250
|
+
selected.push(line);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
lines.slice(0, 24).forEach(addLine);
|
|
254
|
+
for (const line of lines) {
|
|
255
|
+
if (/^\s*#{1,6}\s+/.test(line) || IMPORTANT_REVISION_LINE.test(line)) {
|
|
256
|
+
addLine(line);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
lines.slice(-24).forEach(addLine);
|
|
260
|
+
|
|
261
|
+
const preface = [
|
|
262
|
+
`[Quiver compacted oversized revise input from ${originalBytes} bytes before provider execution.]`,
|
|
263
|
+
'[Preserved headings and lines mentioning decisions, risks, files, acceptance criteria, validation, blockers, dependencies, assumptions, pending work, rollback, and evidence.]',
|
|
264
|
+
'',
|
|
265
|
+
].join('\n');
|
|
266
|
+
const compacted = `${preface}${selected.join('\n')}`;
|
|
267
|
+
const targetBytes = Math.min(limits.compactedRevisionInputBytes, limits.maxRevisionInputBytes);
|
|
268
|
+
const finalText = compactTextToByteLimit(compacted, targetBytes);
|
|
269
|
+
const compactedBytes = byteLength(finalText);
|
|
270
|
+
|
|
271
|
+
if (compactedBytes > limits.maxRevisionInputBytes) {
|
|
272
|
+
const error = new Error(formatError(`ai revise input is too large after compaction (${compactedBytes} bytes; limit ${limits.maxRevisionInputBytes}). Reduce feedback size and retry.`));
|
|
273
|
+
error.code = 'AI_INPUT_TOO_LARGE';
|
|
274
|
+
throw error;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
text: `${finalText.trimEnd()}\n`,
|
|
279
|
+
compaction: {
|
|
280
|
+
compacted: true,
|
|
281
|
+
original_bytes: originalBytes,
|
|
282
|
+
compacted_bytes: compactedBytes,
|
|
283
|
+
max_revision_input_bytes: limits.maxRevisionInputBytes,
|
|
284
|
+
preserved: ['headings', 'decisions', 'risks', 'files', 'acceptance criteria', 'validation', 'blockers', 'dependencies', 'assumptions', 'rollback', 'evidence'],
|
|
285
|
+
},
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function assertProviderPromptWithinLimit(prompt, options = {}) {
|
|
290
|
+
const limits = resolveAiArtifactLimits(options);
|
|
291
|
+
const promptBytes = byteLength(prompt);
|
|
292
|
+
|
|
293
|
+
if (promptBytes <= limits.maxProviderPromptBytes) {
|
|
294
|
+
return {
|
|
295
|
+
prompt,
|
|
296
|
+
bytes: promptBytes,
|
|
297
|
+
maxProviderPromptBytes: limits.maxProviderPromptBytes,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const error = new Error(formatError(`provider prompt is too large (${promptBytes} bytes; limit ${limits.maxProviderPromptBytes}). Reduce the input, split the work, or run ai revise with focused feedback before invoking the provider.`));
|
|
302
|
+
error.code = 'AI_PROMPT_TOO_LARGE';
|
|
303
|
+
throw error;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = {
|
|
307
|
+
DEFAULT_COMPACTED_REVISION_INPUT_BYTES,
|
|
308
|
+
DEFAULT_MAX_PROVIDER_PROMPT_BYTES,
|
|
309
|
+
DEFAULT_MAX_REVISION_INPUT_BYTES,
|
|
310
|
+
RAW_ARTIFACT_SCHEMA_VERSION,
|
|
311
|
+
assertProviderPromptWithinLimit,
|
|
312
|
+
byteLength,
|
|
313
|
+
compactRevisionInput,
|
|
314
|
+
extractCleanProviderOutput,
|
|
315
|
+
redactSensitiveLocalValues,
|
|
316
|
+
resolveAiArtifactLimits,
|
|
317
|
+
writeRawProviderArtifact,
|
|
318
|
+
};
|
|
@@ -3,6 +3,7 @@ const path = require('node:path');
|
|
|
3
3
|
|
|
4
4
|
const { resolveProfileProvider } = require('../agent-profiles');
|
|
5
5
|
const { branchDelete, runGit, statusPorcelain, worktreeAdd, worktreePrune, worktreeRemove } = require('../git');
|
|
6
|
+
const { withLock } = require('../locks');
|
|
6
7
|
const { safeBranchName, worktreesRootForRepo } = require('../slice');
|
|
7
8
|
const { buildGraph, computeLevels, detectFileConflicts, isFoundationSliceId, readAllSlices, topoSort, SliceGraphError } = require('../slice-graph');
|
|
8
9
|
const { runExecuteSlice } = require('./executor');
|
|
@@ -486,6 +487,13 @@ async function runParallelGroupInWorktrees(repoRoot, level, group, options = {})
|
|
|
486
487
|
const slices = group.slice_refs.map((ref) => level.slices.find((item) => item.ref === ref));
|
|
487
488
|
const workspaces = slices.map((slice, index) => buildDelegatedWorkspace(repoRoot, slice, runId, index, options));
|
|
488
489
|
|
|
490
|
+
return withLock(repoRoot, `execute-plan-${runId}`, {
|
|
491
|
+
command: 'ai execute-plan',
|
|
492
|
+
metadata: {
|
|
493
|
+
mode: 'delegated',
|
|
494
|
+
slices: group.slice_refs,
|
|
495
|
+
},
|
|
496
|
+
}, async () => {
|
|
489
497
|
let runResults;
|
|
490
498
|
try {
|
|
491
499
|
worktreePrune(repoRoot);
|
|
@@ -542,6 +550,7 @@ async function runParallelGroupInWorktrees(repoRoot, level, group, options = {})
|
|
|
542
550
|
workspace: item.workspace.worktreePath,
|
|
543
551
|
integratedCommit: item.commit,
|
|
544
552
|
}));
|
|
553
|
+
});
|
|
545
554
|
}
|
|
546
555
|
|
|
547
556
|
async function runExecutePlan(repoRoot, options = {}) {
|