create-quiver 0.12.1 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +24 -9
  3. package/README_FOR_AI.md +15 -6
  4. package/ROADMAP.md +15 -2
  5. package/docs/COMMANDS.md.template +12 -3
  6. package/docs/TROUBLESHOOTING.md.template +29 -0
  7. package/docs/WORKFLOW.md.template +13 -12
  8. package/package.json +2 -1
  9. package/specs/quiver-v26-0121-smoke-hardening/SPEC.md +2 -2
  10. package/specs/quiver-v26-0121-smoke-hardening/STATUS.md +5 -5
  11. package/specs/quiver-v27-reliability-ai-workflow-hardening/AUDIT_V24_V25_V26.md +67 -0
  12. package/specs/quiver-v27-reliability-ai-workflow-hardening/COMMAND_CONTRACTS.md +125 -0
  13. package/specs/quiver-v27-reliability-ai-workflow-hardening/COVERAGE_MATRIX.md +74 -0
  14. package/specs/quiver-v27-reliability-ai-workflow-hardening/EVIDENCE_REPORT.md +179 -0
  15. package/specs/quiver-v27-reliability-ai-workflow-hardening/EXECUTION_PLAN.md +71 -0
  16. package/specs/quiver-v27-reliability-ai-workflow-hardening/SPEC.md +176 -0
  17. package/specs/quiver-v27-reliability-ai-workflow-hardening/STATUS.md +37 -0
  18. package/specs/quiver-v27-reliability-ai-workflow-hardening/pr.md +132 -0
  19. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/CLOSURE_BRIEF.md +36 -0
  20. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/EXECUTION_BRIEF.md +56 -0
  21. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-00-docs-audit-coverage-and-contracts/slice.json +75 -0
  22. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/CLOSURE_BRIEF.md +37 -0
  23. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/EXECUTION_BRIEF.md +54 -0
  24. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-01-core-state-resolver-and-canonical-statuses/slice.json +79 -0
  25. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/CLOSURE_BRIEF.md +34 -0
  26. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/EXECUTION_BRIEF.md +54 -0
  27. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-02-json-export-contract-and-machine-output/slice.json +75 -0
  28. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/CLOSURE_BRIEF.md +36 -0
  29. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/EXECUTION_BRIEF.md +55 -0
  30. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-03-approved-plan-to-spec-create/slice.json +78 -0
  31. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/CLOSURE_BRIEF.md +31 -0
  32. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/EXECUTION_BRIEF.md +55 -0
  33. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-04-ai-artifact-storage-redaction-and-token-compaction/slice.json +77 -0
  34. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/CLOSURE_BRIEF.md +31 -0
  35. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/EXECUTION_BRIEF.md +55 -0
  36. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-05-worktree-lifecycle-locks-and-recovery/slice.json +84 -0
  37. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/CLOSURE_BRIEF.md +32 -0
  38. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/EXECUTION_BRIEF.md +57 -0
  39. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-06-validation-gates-and-scope-safety/slice.json +99 -0
  40. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/CLOSURE_BRIEF.md +31 -0
  41. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/EXECUTION_BRIEF.md +57 -0
  42. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-07-context-analysis-and-doctor-flow/slice.json +88 -0
  43. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/CLOSURE_BRIEF.md +31 -0
  44. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/EXECUTION_BRIEF.md +56 -0
  45. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-08-cross-platform-help-auth-and-dx/slice.json +85 -0
  46. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/CLOSURE_BRIEF.md +32 -0
  47. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/EXECUTION_BRIEF.md +56 -0
  48. package/specs/quiver-v27-reliability-ai-workflow-hardening/slices/slice-09-fixtures-smoke-docs-and-release-readiness/slice.json +91 -0
  49. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/COVERAGE_MATRIX.md +117 -0
  50. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/EVIDENCE_REPORT.md +200 -0
  51. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/EXECUTION_PLAN.md +60 -0
  52. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/SPEC.md +132 -0
  53. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/STATUS.md +36 -0
  54. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/pr.md +128 -0
  55. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/CLOSURE_BRIEF.md +44 -0
  56. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/EXECUTION_BRIEF.md +56 -0
  57. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/slice.json +71 -0
  58. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/CLOSURE_BRIEF.md +38 -0
  59. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/EXECUTION_BRIEF.md +53 -0
  60. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/slice.json +83 -0
  61. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/CLOSURE_BRIEF.md +33 -0
  62. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/EXECUTION_BRIEF.md +53 -0
  63. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/slice.json +85 -0
  64. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/CLOSURE_BRIEF.md +34 -0
  65. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/EXECUTION_BRIEF.md +52 -0
  66. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/slice.json +82 -0
  67. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/CLOSURE_BRIEF.md +32 -0
  68. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/EXECUTION_BRIEF.md +55 -0
  69. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/slice.json +85 -0
  70. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/CLOSURE_BRIEF.md +35 -0
  71. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/EXECUTION_BRIEF.md +59 -0
  72. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/slice.json +94 -0
  73. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/CLOSURE_BRIEF.md +40 -0
  74. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/EXECUTION_BRIEF.md +56 -0
  75. package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/slice.json +98 -0
  76. package/src/create-quiver/commands/ai.js +563 -21
  77. package/src/create-quiver/commands/flow.js +52 -4
  78. package/src/create-quiver/commands/graph.js +7 -7
  79. package/src/create-quiver/commands/plan.js +6 -15
  80. package/src/create-quiver/commands/spec.js +292 -0
  81. package/src/create-quiver/index.js +125 -25
  82. package/src/create-quiver/lib/agent-profiles.js +15 -3
  83. package/src/create-quiver/lib/ai/artifacts.js +318 -0
  84. package/src/create-quiver/lib/ai/context-packs.js +2 -2
  85. package/src/create-quiver/lib/ai/execution-plan.js +9 -0
  86. package/src/create-quiver/lib/ai/executor.js +3 -2
  87. package/src/create-quiver/lib/ai/export-state.js +287 -95
  88. package/src/create-quiver/lib/ai/github.js +93 -4
  89. package/src/create-quiver/lib/ai/plan-review.js +161 -0
  90. package/src/create-quiver/lib/ai/run-state.js +17 -2
  91. package/src/create-quiver/lib/ai/spec-generator.js +87 -13
  92. package/src/create-quiver/lib/ai/spec-templates.js +72 -12
  93. package/src/create-quiver/lib/analyze.js +2 -2
  94. package/src/create-quiver/lib/approvals.js +14 -2
  95. package/src/create-quiver/lib/doctor.js +79 -0
  96. package/src/create-quiver/lib/git.js +40 -1
  97. package/src/create-quiver/lib/handoff.js +43 -1
  98. package/src/create-quiver/lib/init-docs.js +11 -7
  99. package/src/create-quiver/lib/init-layout.js +1 -0
  100. package/src/create-quiver/lib/lifecycle.js +52 -3
  101. package/src/create-quiver/lib/locks.js +134 -0
  102. package/src/create-quiver/lib/package-safety.js +7 -0
  103. package/src/create-quiver/lib/paths.js +74 -0
  104. package/src/create-quiver/lib/project-scan.js +74 -0
  105. package/src/create-quiver/lib/project-state-resolver.js +430 -0
  106. package/src/create-quiver/lib/readiness.js +48 -7
  107. package/src/create-quiver/lib/scope.js +2 -1
  108. package/src/create-quiver/lib/slice.js +8 -4
  109. package/src/create-quiver/lib/spec-worktrees.js +169 -38
  110. package/src/create-quiver/lib/statuses.js +115 -0
@@ -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
+ };
@@ -27,14 +27,14 @@ const CONTEXT_PACKS = Object.freeze({
27
27
  description: 'Executor context for a single slice handoff.',
28
28
  role: ROLES.EXECUTOR,
29
29
  tokenBudgetHint: 3200,
30
- roleGuidance: 'Use slice handoff, allowed files, acceptance criteria, and validation commands only.',
30
+ roleGuidance: 'Use the slice.json, EXECUTION_BRIEF, CLOSURE_BRIEF, allowed files, acceptance criteria, and validation commands only. Do not request the full spec unless the slice brief explicitly requires it.',
31
31
  }),
32
32
  minimal: Object.freeze({
33
33
  name: 'minimal',
34
34
  description: 'Smallest executor context for narrowly-scoped tasks.',
35
35
  role: ROLES.EXECUTOR,
36
36
  tokenBudgetHint: 1200,
37
- roleGuidance: 'Use the smallest safe set of slice details and avoid onboarding context.',
37
+ roleGuidance: 'Use the smallest safe set of slice details, avoid onboarding context, and avoid full-spec context by default.',
38
38
  }),
39
39
  });
40
40
 
@@ -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 = {}) {
@@ -9,6 +9,7 @@ const { currentBranch, runGit } = require('../git');
9
9
  const { redactSecrets, truncateText } = require('../evidence');
10
10
  const { captureWorktreeSnapshot, validateScopeSnapshot } = require('../scope');
11
11
  const { resolveSliceContext } = require('../slice');
12
+ const { validateProjectRelativePaths } = require('../paths');
12
13
 
13
14
  const DEFAULT_EXECUTE_PROVIDER = 'codex';
14
15
  const DEFAULT_EXECUTE_ROLE = 'executor';
@@ -303,8 +304,8 @@ function buildExecuteSliceContext({ repoRoot, slicePath, role, context }) {
303
304
  });
304
305
  const relativeSlicePath = toRelativePath(canonicalRepoRoot, slice.sliceAbs);
305
306
  const relativeBriefPath = toRelativePath(canonicalRepoRoot, briefPath);
306
- const allowedFiles = Array.isArray(slice.files) ? slice.files.map((file) => String(file)) : [];
307
- const expectedReadPaths = Array.isArray(slice.expectedReadPaths) ? slice.expectedReadPaths.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');
308
309
  const acceptance = Array.isArray(slice.acceptance) ? slice.acceptance.map((item) => String(item)) : [];
309
310
  const validationCommands = Array.isArray(slice.tests) ? slice.tests.map((item) => String(item)) : [];
310
311
  const validationHints = Array.isArray(slice.validationHints) ? slice.validationHints.map((item) => String(item)) : [];