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.
- package/CHANGELOG.md +27 -0
- package/README.md +24 -9
- package/README_FOR_AI.md +15 -6
- package/ROADMAP.md +15 -2
- package/docs/COMMANDS.md.template +12 -3
- package/docs/TROUBLESHOOTING.md.template +29 -0
- package/docs/WORKFLOW.md.template +13 -12
- 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/specs/quiver-v28-pixel-quiver-feedback-reconciliation/COVERAGE_MATRIX.md +117 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/EVIDENCE_REPORT.md +200 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/EXECUTION_PLAN.md +60 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/SPEC.md +132 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/STATUS.md +36 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/pr.md +128 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/CLOSURE_BRIEF.md +44 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-00-reconciliation-and-evidence-freeze/slice.json +71 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/CLOSURE_BRIEF.md +38 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-01-ai-run-state-approvals-and-clean-output/slice.json +83 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/CLOSURE_BRIEF.md +33 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/EXECUTION_BRIEF.md +53 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-02-structured-technical-plan-contract-and-repair-flow/slice.json +85 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/CLOSURE_BRIEF.md +34 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/EXECUTION_BRIEF.md +52 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-03-active-slice-reconciliation-and-ai-inspect/slice.json +82 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/CLOSURE_BRIEF.md +32 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/EXECUTION_BRIEF.md +55 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-04-spec-validation-scope-and-worktree-reliability/slice.json +85 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/CLOSURE_BRIEF.md +35 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/EXECUTION_BRIEF.md +59 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-05-review-plan-closure-and-agent-dx/slice.json +94 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/CLOSURE_BRIEF.md +40 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/EXECUTION_BRIEF.md +56 -0
- package/specs/quiver-v28-pixel-quiver-feedback-reconciliation/slices/slice-06-backward-compatibility-docs-and-release-readiness/slice.json +98 -0
- package/src/create-quiver/commands/ai.js +563 -21
- 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 +292 -0
- package/src/create-quiver/index.js +125 -25
- 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/context-packs.js +2 -2
- 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 +287 -95
- package/src/create-quiver/lib/ai/github.js +93 -4
- package/src/create-quiver/lib/ai/plan-review.js +161 -0
- package/src/create-quiver/lib/ai/run-state.js +17 -2
- package/src/create-quiver/lib/ai/spec-generator.js +87 -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 +430 -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 +169 -38
- 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
|
|
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
|
|
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)) : [];
|