vibe-coding-master 0.7.42 → 0.7.44
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/README.md +49 -17
- package/dist/backend/api/artifact-routes.js +3 -0
- package/dist/backend/api/harness-routes.js +16 -0
- package/dist/backend/api/task-routes.js +32 -2
- package/dist/backend/api/workflow-control-routes.js +7 -26
- package/dist/backend/cli/install-vcm-harness.js +61 -6
- package/dist/backend/role-tool-policy.js +1 -1
- package/dist/backend/server.js +14 -5
- package/dist/backend/services/artifact-service.js +10 -32
- package/dist/backend/services/auto-memory-service.js +642 -14
- package/dist/backend/services/claude-hook-service.js +183 -6
- package/dist/backend/services/gate-review-service.js +173 -74
- package/dist/backend/services/harness-feedback-service.js +180 -80
- package/dist/backend/services/harness-service.js +65 -9
- package/dist/backend/services/memory-review-paths.js +13 -0
- package/dist/backend/services/role-stall-detector-service.js +322 -0
- package/dist/backend/services/round-service.js +25 -0
- package/dist/backend/services/runtime-coordinator-service.js +12 -1
- package/dist/backend/services/session-service.js +70 -3
- package/dist/backend/services/status-service.js +1 -0
- package/dist/backend/services/translation-worker-service.js +19 -4
- package/dist/backend/services/workflow-control-service.js +524 -204
- package/dist/backend/templates/handoff.js +46 -5
- package/dist/backend/templates/harness/architect-agent.js +12 -6
- package/dist/backend/templates/harness/architect-scaffold-worker-agent.js +1 -1
- package/dist/backend/templates/harness/check-scaffold-ledger.js +234 -10
- package/dist/backend/templates/harness/claude-root.js +3 -2
- package/dist/backend/templates/harness/coder-agent.js +12 -5
- package/dist/backend/templates/harness/gate-review.js +150 -57
- package/dist/backend/templates/harness/harness-engineer-agent.js +38 -13
- package/dist/backend/templates/harness/project-manager-agent.js +18 -12
- package/dist/backend/templates/harness/resolve-durable-doc-assignment.js +60 -0
- package/dist/backend/templates/harness/tester-agent.js +13 -0
- package/dist/backend/templates/harness/vcm-ask-user-skill.js +82 -0
- package/dist/backend/templates/harness/vcm-code-navigation-skill.js +7 -6
- package/dist/backend/templates/harness/vcm-task-state-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-workflow-review-skill.js +7 -9
- package/dist/shared/types/role-stall.js +1 -0
- package/dist/shared/types/workflow.js +15 -0
- package/dist/shared/validation/artifact-check.js +3 -3
- package/dist/shared/validation/artifact-contract.js +1 -1
- package/dist/shared/validation/artifact-registry.js +17 -1
- package/dist-frontend/assets/{index-C_XHGNBD.css → index-B0d4Z6ny.css} +1 -1
- package/dist-frontend/assets/index-BvCmrFlN.js +97 -0
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/claude-plugins/vcm-lsp-bridge/.claude-plugin/plugin.json +21 -6
- package/scripts/harness-tools/vcm-artifact +1 -2
- package/scripts/harness-tools/vcm-bash-guard +204 -14
- package/dist-frontend/assets/index-Bocc2DWF.js +0 -97
|
@@ -4,7 +4,6 @@ import { checkMarkdownArtifact, readArtifactSectionValue } from "../../shared/va
|
|
|
4
4
|
import { resolveRepoPath } from "../adapters/filesystem.js";
|
|
5
5
|
import { VcmError } from "../errors.js";
|
|
6
6
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
7
|
-
import { getRetrospectiveReportErrors } from "./artifact-service.js";
|
|
8
7
|
const FEEDBACK_ROOT = ".ai/vcm/harness-feedback";
|
|
9
8
|
const PENDING_DIR = `${FEEDBACK_ROOT}/pending`;
|
|
10
9
|
const TASK_RETROSPECTIVE_DIR = `${FEEDBACK_ROOT}/task-retrospectives`;
|
|
@@ -76,6 +75,7 @@ export function createHarnessFeedbackService(deps) {
|
|
|
76
75
|
});
|
|
77
76
|
}
|
|
78
77
|
const session = await ensureIdleHarnessEngineer(repoRoot, taskSlug);
|
|
78
|
+
const pendingFeedback = await listPendingFeedback(repoRoot);
|
|
79
79
|
const timestamp = now();
|
|
80
80
|
const analysisPath = `${TASK_RETROSPECTIVE_DIR}/${sanitizeFeedbackId(taskSlug)}.md`;
|
|
81
81
|
const analysisAbsolutePath = resolveRepoPath(repoRoot, analysisPath);
|
|
@@ -87,11 +87,11 @@ export function createHarnessFeedbackService(deps) {
|
|
|
87
87
|
status: "running",
|
|
88
88
|
analysisPath,
|
|
89
89
|
finalAcceptanceHash: `sha256:${sha256(finalAcceptanceContent)}`,
|
|
90
|
+
pendingFeedbackPaths: pendingFeedback.map((item) => item.path),
|
|
90
91
|
...(memoryReview ? { memoryRunId: memoryReview.runId } : {}),
|
|
91
92
|
createdAt: timestamp,
|
|
92
93
|
updatedAt: timestamp
|
|
93
94
|
};
|
|
94
|
-
const pendingFeedback = await listPendingFeedback(repoRoot);
|
|
95
95
|
try {
|
|
96
96
|
await persistTaskRetrospectiveMarker(repoRoot, marker);
|
|
97
97
|
await submitTerminalInput(deps.runtime, session.id, buildTaskRetrospectivePrompt(repoRoot, analysisPath, pendingFeedback.map((item) => item.path), memoryReview));
|
|
@@ -135,9 +135,14 @@ export function createHarnessFeedbackService(deps) {
|
|
|
135
135
|
const reportContent = await deps.fs.pathExists(reportPath)
|
|
136
136
|
? await deps.fs.readText(reportPath)
|
|
137
137
|
: "";
|
|
138
|
-
const reportErrors = reportContent.trim()
|
|
138
|
+
const reportErrors = reportContent.trim()
|
|
139
|
+
? [
|
|
140
|
+
...getRetrospectiveReportErrors(reportContent),
|
|
141
|
+
...getFeedbackDispositionErrors(repoRoot, marker.pendingFeedbackPaths ?? [], reportContent)
|
|
142
|
+
]
|
|
143
|
+
: ["Report is empty."];
|
|
139
144
|
const reportReady = reportErrors.length === 0;
|
|
140
|
-
if (!reportReady || (marker.memoryRunId &&
|
|
145
|
+
if (!reportReady || (marker.memoryRunId && input.memoryReviewStatus === "failed")) {
|
|
141
146
|
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
142
147
|
...marker,
|
|
143
148
|
status: "failed",
|
|
@@ -149,13 +154,60 @@ export function createHarnessFeedbackService(deps) {
|
|
|
149
154
|
});
|
|
150
155
|
return true;
|
|
151
156
|
}
|
|
157
|
+
if (marker.memoryRunId && input.memoryReviewStatus === "documenting") {
|
|
158
|
+
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
159
|
+
...marker,
|
|
160
|
+
status: "waiting-docs",
|
|
161
|
+
updatedAt: timestamp
|
|
162
|
+
});
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
await completeTaskRetrospective(repoRoot, marker);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
async function completeWaitingTaskRetrospective(repoRoot, taskSlug, memoryStatus) {
|
|
169
|
+
const marker = await loadTaskRetrospectiveMarker(repoRoot, taskSlug);
|
|
170
|
+
if (!marker || marker.status !== "waiting-docs") {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (memoryStatus === "documenting") {
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
if (memoryStatus === "failed") {
|
|
177
|
+
const timestamp = now();
|
|
178
|
+
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
179
|
+
...marker,
|
|
180
|
+
status: "failed",
|
|
181
|
+
failedAt: timestamp,
|
|
182
|
+
updatedAt: timestamp,
|
|
183
|
+
error: "Task Harness Retrospective durable-document assignment failed."
|
|
184
|
+
});
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
await completeTaskRetrospective(repoRoot, marker);
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
async function completeTaskRetrospective(repoRoot, marker) {
|
|
191
|
+
const timestamp = now();
|
|
192
|
+
try {
|
|
193
|
+
await removeProcessedFeedback(repoRoot, marker.pendingFeedbackPaths ?? []);
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
197
|
+
...marker,
|
|
198
|
+
status: "failed",
|
|
199
|
+
failedAt: timestamp,
|
|
200
|
+
updatedAt: timestamp,
|
|
201
|
+
error: `VCM could not remove processed Harness Feedback: ${errorMessage(error)}`
|
|
202
|
+
});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
152
205
|
await persistTaskRetrospectiveMarker(repoRoot, {
|
|
153
206
|
...marker,
|
|
154
207
|
status: "completed",
|
|
155
208
|
completedAt: timestamp,
|
|
156
209
|
updatedAt: timestamp
|
|
157
210
|
});
|
|
158
|
-
return true;
|
|
159
211
|
}
|
|
160
212
|
async function assertHarnessEngineerAvailable(_repoRoot) {
|
|
161
213
|
return undefined;
|
|
@@ -245,7 +297,15 @@ export function createHarnessFeedbackService(deps) {
|
|
|
245
297
|
...(pendingFeedbackPaths.length > 0
|
|
246
298
|
? [
|
|
247
299
|
"",
|
|
248
|
-
"Process every listed feedback inside this retrospective. Record every disposition in the retrospective report
|
|
300
|
+
"Process every listed feedback inside this retrospective. Record every disposition in the retrospective report using this exact block for each assigned path:",
|
|
301
|
+
"",
|
|
302
|
+
"### Feedback: <exact assigned absolute path>",
|
|
303
|
+
"Decision: confirmed|rejected|duplicate|already-covered",
|
|
304
|
+
"Evidence: <concise evidence>",
|
|
305
|
+
"Impact: <impact>",
|
|
306
|
+
"Required action: <action or none>",
|
|
307
|
+
"",
|
|
308
|
+
"Do not edit or delete pending feedback files. VCM removes the assigned files after validating every disposition in the accepted report."
|
|
249
309
|
]
|
|
250
310
|
: []),
|
|
251
311
|
"",
|
|
@@ -258,6 +318,16 @@ export function createHarnessFeedbackService(deps) {
|
|
|
258
318
|
`Current memory snapshot: ${memoryReview.currentMemoryPath}`,
|
|
259
319
|
"Active memory files:",
|
|
260
320
|
...memoryReview.activeMemoryPaths.map((memoryPath) => `- ${memoryPath}`),
|
|
321
|
+
"Proposal candidates:",
|
|
322
|
+
...(memoryReview.proposalCandidates.length > 0
|
|
323
|
+
? memoryReview.proposalCandidates.map((candidate) => [
|
|
324
|
+
candidate.id,
|
|
325
|
+
`source=${candidate.source}`,
|
|
326
|
+
`operation=${candidate.operation}`,
|
|
327
|
+
`target=${candidate.target}`,
|
|
328
|
+
`entry=${candidate.content ?? candidate.existing ?? "none"}`
|
|
329
|
+
].join(" | "))
|
|
330
|
+
: ["none"]),
|
|
261
331
|
...(memoryReview.planningCandidatePath
|
|
262
332
|
? [`Architect planning-session candidate: ${memoryReview.planningCandidatePath}`]
|
|
263
333
|
: []),
|
|
@@ -265,88 +335,30 @@ export function createHarnessFeedbackService(deps) {
|
|
|
265
335
|
"Review every memory candidate against final task evidence while performing this retrospective.",
|
|
266
336
|
"The snapshot files contain only the matching pre-review <VCM-memory> block content.",
|
|
267
337
|
"Before evaluating proposals, review every substantive entry in every current memory snapshot against current code, documentation, and final task evidence.",
|
|
268
|
-
"
|
|
269
|
-
"
|
|
270
|
-
"
|
|
271
|
-
"
|
|
272
|
-
"
|
|
273
|
-
|
|
274
|
-
"
|
|
275
|
-
"
|
|
276
|
-
"
|
|
277
|
-
"
|
|
278
|
-
"
|
|
279
|
-
"Do not keep full content in memory when durable-doc is correct. Use memory-reference only when an ongoing role needs the document pointer.",
|
|
280
|
-
"Use none as the complete Existing Memory Decisions body only when no substantive existing memory entry exists.",
|
|
281
|
-
"Apply the reviewed result directly to the <VCM-memory> blocks in the listed active memory files. Do not change any content outside those blocks.",
|
|
282
|
-
"If memory changes, commit only the changed active memory files before ending the turn. Use commit message: chore: update VCM memory. If memory is unchanged, do not create a commit.",
|
|
283
|
-
"Use this exact block in the retrospective report and replace each option or placeholder with one allowed value or a concise summary:",
|
|
338
|
+
"Evaluate each proposal independently. Keep only verified, durable, reusable project knowledge; do not keep task narrative, temporary state, unverified conclusions, or Harness rules in memory.",
|
|
339
|
+
"For every existing entry and proposal, record why the decision is necessary, the impact if the knowledge is absent, the evidence checked, and whether a durable document is the correct source.",
|
|
340
|
+
"When the decision is move-to-durable-doc, remove the entry from memory now and add one durableDocAssignment. Do not wait for the durable document update before removing memory.",
|
|
341
|
+
"Apply the reviewed result directly to the listed <VCM-memory> blocks. Do not change content outside those blocks.",
|
|
342
|
+
"If memory changes, commit only the changed active memory files with message [VCM Harness] Update VCM memory. If memory is unchanged, do not create a commit.",
|
|
343
|
+
`Write the complete machine-readable review to: ${memoryReview.reviewResultPath}`,
|
|
344
|
+
"The JSON root must be: {\"version\":1,\"runId\":\"<assigned run id>\",\"memoryCommit\":\"<full commit or none>\",\"decisions\":[],\"durableDocAssignments\":[]}.",
|
|
345
|
+
"Each decision must contain itemId, source (existing|proposal), target, entry, decision, reason, impactIfAbsent, evidence (non-empty array), finalContent, and durableDocPath.",
|
|
346
|
+
"Existing decisions use retain|update|remove|move-to-durable-doc. Add or Update proposal decisions use keep-in-memory|keep-memory-reference|move-to-durable-doc|reject; Remove proposal decisions use remove|retain. Proposal itemId must equal the assigned candidate ID.",
|
|
347
|
+
"Each durableDocAssignment must contain sourceMemoryPath, sourceEntry, targetPath, content, reason, and evidence (non-empty array). Use a project-relative Markdown target outside .ai/vcm.",
|
|
348
|
+
"In the retrospective report, include only this memory summary:",
|
|
284
349
|
"",
|
|
285
350
|
"## Memory Review",
|
|
286
|
-
"
|
|
287
|
-
|
|
288
|
-
"
|
|
289
|
-
...(memoryReview.proposalCandidates.length > 0
|
|
290
|
-
? memoryReview.proposalCandidates.flatMap(renderMemoryProposalDecisionTemplate)
|
|
291
|
-
: ["none"]),
|
|
292
|
-
"",
|
|
293
|
-
"### Existing Memory Decisions",
|
|
294
|
-
"#### Item 1",
|
|
295
|
-
"Target: shared",
|
|
296
|
-
"Existing: <exact existing memory entry>",
|
|
297
|
-
"Decision: retain",
|
|
298
|
-
"Reason: <why this decision is correct>",
|
|
299
|
-
"Impact if removed: <specific future role or task failure>",
|
|
300
|
-
"Durable doc disposition: memory",
|
|
301
|
-
"Durable doc path: none",
|
|
302
|
-
"Evidence: <current code, durable documentation, or final task evidence>",
|
|
303
|
-
"",
|
|
304
|
-
"### Existing Memory Changes",
|
|
305
|
-
"- retained: <summary or none>",
|
|
306
|
-
"- updated: <summary or none>",
|
|
307
|
-
"- removed: <summary or none>",
|
|
308
|
-
"",
|
|
309
|
-
"Reviewed memory set: complete"
|
|
351
|
+
"Memory commit: <full commit or none>",
|
|
352
|
+
`Review result: ${memoryReview.reviewResultPath}`,
|
|
353
|
+
"Durable document assignments: <count>"
|
|
310
354
|
]
|
|
311
355
|
: []),
|
|
312
356
|
"",
|
|
313
357
|
`Write the analysis to Result Path: ${resolveRepoPath(repoRoot, analysisPath)}`,
|
|
314
|
-
|
|
315
|
-
"End your turn after
|
|
358
|
+
"Write the report directly to that path. Do not use vcm-artifact.",
|
|
359
|
+
"End your turn after the report is complete."
|
|
316
360
|
].join("\n");
|
|
317
361
|
}
|
|
318
|
-
function renderMemoryProposalDecisionTemplate(candidate) {
|
|
319
|
-
if (candidate.operation === "remove") {
|
|
320
|
-
return [
|
|
321
|
-
`#### Candidate ${candidate.id}`,
|
|
322
|
-
`Source: ${candidate.source}`,
|
|
323
|
-
"Operation: remove",
|
|
324
|
-
`Target: ${candidate.target}`,
|
|
325
|
-
`Existing: ${candidate.existing}`,
|
|
326
|
-
"Decision: remove|retain",
|
|
327
|
-
"Reason: <why the proposed removal should be applied or rejected>",
|
|
328
|
-
"Evidence checked: <current code, durable documentation, or final task evidence>",
|
|
329
|
-
""
|
|
330
|
-
];
|
|
331
|
-
}
|
|
332
|
-
return [
|
|
333
|
-
`#### Candidate ${candidate.id}`,
|
|
334
|
-
`Source: ${candidate.source}`,
|
|
335
|
-
`Operation: ${candidate.operation}`,
|
|
336
|
-
`Target: ${candidate.target}`,
|
|
337
|
-
`Candidate: ${candidate.content}`,
|
|
338
|
-
"Decision: keep-in-memory|keep-memory-reference|move-to-durable-doc|reject",
|
|
339
|
-
"Final target: shared|project-manager|architect|coder|tester|reviewer|harness-engineer|none",
|
|
340
|
-
"Why memory is necessary: <independent reason, or why it is not necessary>",
|
|
341
|
-
"Impact if absent: <specific impact, or why no durable impact exists>",
|
|
342
|
-
"Durable doc disposition: memory|durable-doc|memory-reference",
|
|
343
|
-
"Durable doc analysis: <why this destination is correct>",
|
|
344
|
-
"Durable doc path: <none or a project-relative durable doc path>",
|
|
345
|
-
"Evidence checked: <current code, durable documentation, or final task evidence>",
|
|
346
|
-
"Final content: <exact one-line reviewed-memory content or none>",
|
|
347
|
-
""
|
|
348
|
-
];
|
|
349
|
-
}
|
|
350
362
|
function buildPendingFeedbackPrompt(repoRoot, feedbackPath) {
|
|
351
363
|
return [
|
|
352
364
|
"[VCM Harness Feedback]",
|
|
@@ -375,6 +387,20 @@ export function createHarnessFeedbackService(deps) {
|
|
|
375
387
|
await deps.fs.removePath?.(statePath, { force: true });
|
|
376
388
|
}
|
|
377
389
|
}
|
|
390
|
+
async function removeProcessedFeedback(repoRoot, feedbackPaths) {
|
|
391
|
+
if (feedbackPaths.length === 0) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (!deps.fs.removePath) {
|
|
395
|
+
throw new Error("The filesystem adapter does not support feedback removal.");
|
|
396
|
+
}
|
|
397
|
+
for (const feedbackPath of feedbackPaths) {
|
|
398
|
+
assertPendingFeedbackPath(feedbackPath);
|
|
399
|
+
}
|
|
400
|
+
for (const feedbackPath of feedbackPaths) {
|
|
401
|
+
await deps.fs.removePath(resolveRepoPath(repoRoot, feedbackPath), { force: true });
|
|
402
|
+
}
|
|
403
|
+
}
|
|
378
404
|
async function readOptionalText(repoRoot, relativePath) {
|
|
379
405
|
const absolutePath = resolveRepoPath(repoRoot, relativePath);
|
|
380
406
|
return readAbsoluteOptionalText(absolutePath);
|
|
@@ -390,9 +416,83 @@ export function createHarnessFeedbackService(deps) {
|
|
|
390
416
|
sendPendingFeedback,
|
|
391
417
|
startTaskRetrospective,
|
|
392
418
|
handleTaskRetrospectiveHook,
|
|
419
|
+
completeWaitingTaskRetrospective,
|
|
393
420
|
assertHarnessEngineerAvailable
|
|
394
421
|
};
|
|
395
422
|
}
|
|
423
|
+
function getFeedbackDispositionErrors(repoRoot, feedbackPaths, reportContent) {
|
|
424
|
+
if (feedbackPaths.length === 0) {
|
|
425
|
+
return [];
|
|
426
|
+
}
|
|
427
|
+
const section = readLevelTwoSection(reportContent, "Feedback Dispositions");
|
|
428
|
+
if (!section) {
|
|
429
|
+
return ["Feedback Dispositions is empty."];
|
|
430
|
+
}
|
|
431
|
+
const errors = [];
|
|
432
|
+
const lines = section.split(/\r?\n/);
|
|
433
|
+
for (const feedbackPath of feedbackPaths) {
|
|
434
|
+
try {
|
|
435
|
+
assertPendingFeedbackPath(feedbackPath);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
errors.push(errorMessage(error));
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
const absolutePath = resolveRepoPath(repoRoot, feedbackPath);
|
|
442
|
+
const heading = `### Feedback: ${absolutePath}`;
|
|
443
|
+
const start = lines.findIndex((line) => line.trim() === heading);
|
|
444
|
+
if (start < 0) {
|
|
445
|
+
errors.push(`Missing disposition for assigned feedback: ${absolutePath}.`);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
const endOffset = lines.slice(start + 1).findIndex((line) => /^###\s+/.test(line.trim()));
|
|
449
|
+
const end = endOffset < 0 ? lines.length : start + 1 + endOffset;
|
|
450
|
+
const block = lines.slice(start + 1, end);
|
|
451
|
+
const decision = readDispositionField(block, "Decision");
|
|
452
|
+
if (!decision || !["confirmed", "rejected", "duplicate", "already-covered"].includes(decision)) {
|
|
453
|
+
errors.push(`Feedback disposition Decision for ${absolutePath} must be confirmed|rejected|duplicate|already-covered.`);
|
|
454
|
+
}
|
|
455
|
+
for (const field of ["Evidence", "Impact", "Required action"]) {
|
|
456
|
+
if (!readDispositionField(block, field)) {
|
|
457
|
+
errors.push(`Feedback disposition ${field} is required for ${absolutePath}.`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return errors;
|
|
462
|
+
}
|
|
463
|
+
function readLevelTwoSection(content, heading) {
|
|
464
|
+
const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
465
|
+
const match = new RegExp(`^##\\s+${escapedHeading}\\s*$`, "im").exec(content);
|
|
466
|
+
if (!match || match.index === undefined) {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
const afterHeading = content.slice(match.index + match[0].length);
|
|
470
|
+
const nextHeading = /\n##\s+\S/.exec(afterHeading);
|
|
471
|
+
return (nextHeading ? afterHeading.slice(0, nextHeading.index) : afterHeading).trim();
|
|
472
|
+
}
|
|
473
|
+
function readDispositionField(lines, field) {
|
|
474
|
+
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
475
|
+
return lines
|
|
476
|
+
.map((line) => new RegExp(`^${escapedField}:\\s*(.+)$`, "i").exec(line.trim())?.[1]?.trim())
|
|
477
|
+
.find((value) => Boolean(value));
|
|
478
|
+
}
|
|
479
|
+
function assertPendingFeedbackPath(feedbackPath) {
|
|
480
|
+
if (!/^\.ai\/vcm\/harness-feedback\/pending\/[A-Za-z0-9._-]+\.md$/.test(feedbackPath)) {
|
|
481
|
+
throw new Error(`Invalid assigned Harness Feedback path: ${feedbackPath}.`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function getRetrospectiveReportErrors(content) {
|
|
485
|
+
const errors = [];
|
|
486
|
+
if (!/^# Task Harness Retrospective(?::\s*.+)?\s*$/m.test(content)) {
|
|
487
|
+
errors.push("Retrospective report requires '# Task Harness Retrospective: <task>'.");
|
|
488
|
+
}
|
|
489
|
+
for (const heading of ["Findings", "Feedback Dispositions", "Recommended Harness Changes", "VCM Issue Drafts"]) {
|
|
490
|
+
if (!new RegExp(`^## ${heading}\\s*$`, "m").test(content)) {
|
|
491
|
+
errors.push(`Missing required section: ${heading}.`);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return errors;
|
|
495
|
+
}
|
|
396
496
|
function parseSimpleMetadata(content) {
|
|
397
497
|
const result = {};
|
|
398
498
|
for (const line of content.split(/\r?\n/).slice(0, 80)) {
|
|
@@ -25,10 +25,12 @@ import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/v
|
|
|
25
25
|
import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propose-memory-skill.js";
|
|
26
26
|
import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
|
|
27
27
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
28
|
+
import { renderAskUserTool, renderVcmAskUserSkillRules } from "../templates/harness/vcm-ask-user-skill.js";
|
|
28
29
|
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
29
30
|
import { renderVcmWorkflowReviewSkillRules } from "../templates/harness/vcm-workflow-review-skill.js";
|
|
30
31
|
import { renderCheckScaffoldLedgerTool } from "../templates/harness/check-scaffold-ledger.js";
|
|
31
32
|
import { renderRequestArchitectRestartTool, renderRestartArchitectSkillRules } from "../templates/harness/restart-architect-skill.js";
|
|
33
|
+
import { renderResolveDurableDocAssignmentTool } from "../templates/harness/resolve-durable-doc-assignment.js";
|
|
32
34
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
33
35
|
import { VcmError } from "../errors.js";
|
|
34
36
|
import { bumpHarnessRevision, readHarnessRevisionState } from "./harness-revision.js";
|
|
@@ -57,8 +59,15 @@ const VCM_HOOK_DEFINITIONS = [
|
|
|
57
59
|
{ eventName: "PreToolUse", matcher: "Bash", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
58
60
|
{ eventName: "PreToolUse", matcher: "Write|Edit", command: VCM_BASH_GUARD_HOOK_COMMAND, timeout: 10 },
|
|
59
61
|
{ eventName: "UserPromptSubmit", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
62
|
+
{ eventName: "PreToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
63
|
+
{ eventName: "PostToolUse", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
64
|
+
{ eventName: "PostToolUseFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
65
|
+
{ eventName: "PostToolBatch", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
66
|
+
{ eventName: "SubagentStart", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
67
|
+
{ eventName: "SubagentStop", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
60
68
|
{ eventName: "Stop", command: VCM_STOP_HOOK_COMMAND, timeout: 10 },
|
|
61
69
|
{ eventName: "StopFailure", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
70
|
+
{ eventName: "PreCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
62
71
|
{ eventName: "PostCompact", command: VCM_HOOK_COMMAND, timeout: 5 },
|
|
63
72
|
{ eventName: "PermissionRequest", command: VCM_PERMISSION_REQUEST_HOOK_COMMAND, timeout: 5 }
|
|
64
73
|
];
|
|
@@ -119,7 +128,7 @@ const HARNESS_FILES = [
|
|
|
119
128
|
kind: "skill-vcm-code-navigation",
|
|
120
129
|
path: ".claude/skills/vcm-code-navigation/SKILL.md",
|
|
121
130
|
title: "VCM Code Navigation Skill",
|
|
122
|
-
frontmatter: renderSkillFrontmatter("vcm-code-navigation", "Use when Architect
|
|
131
|
+
frontmatter: renderSkillFrontmatter("vcm-code-navigation", "Use when Architect must resolve code symbols, references, implementations, call hierarchies, or bounded dependency paths."),
|
|
123
132
|
ownership: "whole-file",
|
|
124
133
|
renderRules: renderVcmCodeNavigationSkillRules
|
|
125
134
|
},
|
|
@@ -131,6 +140,14 @@ const HARNESS_FILES = [
|
|
|
131
140
|
ownership: "whole-file",
|
|
132
141
|
renderRules: renderVcmRouteMessageSkillRules
|
|
133
142
|
},
|
|
143
|
+
{
|
|
144
|
+
kind: "skill-vcm-ask-user",
|
|
145
|
+
path: ".claude/skills/vcm-ask-user/SKILL.md",
|
|
146
|
+
title: "VCM Ask User Skill",
|
|
147
|
+
frontmatter: renderSkillFrontmatter("vcm-ask-user", "Use whenever project-manager asks the user a question and must pause the workflow."),
|
|
148
|
+
ownership: "whole-file",
|
|
149
|
+
renderRules: renderVcmAskUserSkillRules
|
|
150
|
+
},
|
|
134
151
|
{
|
|
135
152
|
kind: "skill-vcm-task-state",
|
|
136
153
|
path: ".claude/skills/vcm-task-state/SKILL.md",
|
|
@@ -209,8 +226,8 @@ const HARNESS_FILES = [
|
|
|
209
226
|
title: "Reviewer Agent",
|
|
210
227
|
memoryBlock: true,
|
|
211
228
|
requiredDisallowedTools: REVIEWER_DISALLOWED_TOOLS,
|
|
212
|
-
|
|
213
|
-
frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { disallowedTools: REVIEWER_DISALLOWED_TOOLS.join(", ")
|
|
229
|
+
removedSkills: ["vcm-code-navigation"],
|
|
230
|
+
frontmatter: renderAgentFrontmatter("reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { disallowedTools: REVIEWER_DISALLOWED_TOOLS.join(", ") }),
|
|
214
231
|
renderRules: renderReviewerAgentRules
|
|
215
232
|
},
|
|
216
233
|
{
|
|
@@ -250,6 +267,13 @@ const HARNESS_FILES = [
|
|
|
250
267
|
ownership: "raw-file",
|
|
251
268
|
renderRules: renderRequestGateReviewTool
|
|
252
269
|
},
|
|
270
|
+
{
|
|
271
|
+
kind: "tool-vcm-ask-user",
|
|
272
|
+
path: ".ai/tools/vcm-ask-user",
|
|
273
|
+
title: "VCM Ask User Tool",
|
|
274
|
+
ownership: "raw-file",
|
|
275
|
+
renderRules: renderAskUserTool
|
|
276
|
+
},
|
|
253
277
|
{
|
|
254
278
|
kind: "tool-update-task-state",
|
|
255
279
|
path: ".ai/tools/update-task-state",
|
|
@@ -271,6 +295,13 @@ const HARNESS_FILES = [
|
|
|
271
295
|
ownership: "raw-file",
|
|
272
296
|
renderRules: renderRequestArchitectRestartTool
|
|
273
297
|
},
|
|
298
|
+
{
|
|
299
|
+
kind: "tool-resolve-durable-doc-assignment",
|
|
300
|
+
path: ".ai/tools/resolve-durable-doc-assignment",
|
|
301
|
+
title: "Resolve Durable Documentation Assignment Tool",
|
|
302
|
+
ownership: "raw-file",
|
|
303
|
+
renderRules: renderResolveDurableDocAssignmentTool
|
|
304
|
+
},
|
|
274
305
|
{
|
|
275
306
|
kind: "agent-project-manager",
|
|
276
307
|
path: ".claude/agents/project-manager.md",
|
|
@@ -297,8 +328,8 @@ const HARNESS_FILES = [
|
|
|
297
328
|
title: "Coder Agent",
|
|
298
329
|
memoryBlock: true,
|
|
299
330
|
requiredDisallowedTools: CODE_ROLE_DISALLOWED_TOOLS,
|
|
300
|
-
|
|
301
|
-
frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { disallowedTools: CODE_ROLE_DISALLOWED_TOOLS.join(", ")
|
|
331
|
+
removedSkills: ["vcm-code-navigation"],
|
|
332
|
+
frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { disallowedTools: CODE_ROLE_DISALLOWED_TOOLS.join(", ") }),
|
|
302
333
|
renderRules: renderCoderHarnessRules
|
|
303
334
|
},
|
|
304
335
|
{
|
|
@@ -353,7 +384,7 @@ export function createHarnessService(deps) {
|
|
|
353
384
|
let harnessCommit;
|
|
354
385
|
if (nextContent !== currentContent) {
|
|
355
386
|
await bumpHarnessRevision(deps.fs, repoRoot, now());
|
|
356
|
-
harnessCommit = (await commitHarnessVisibleChanges(deps.git, repoRoot, "
|
|
387
|
+
harnessCommit = (await commitHarnessVisibleChanges(deps.git, repoRoot, "[VCM Harness] Update harness file")).harnessCommit;
|
|
357
388
|
}
|
|
358
389
|
const file = await readHarnessFileContent(deps.fs, repoRoot, definition.path);
|
|
359
390
|
const [analyses, codeIntelligence] = await Promise.all([
|
|
@@ -377,7 +408,7 @@ export function createHarnessService(deps) {
|
|
|
377
408
|
if (result.changedFiles.length > 0) {
|
|
378
409
|
await bumpHarnessRevision(deps.fs, repoRoot, now());
|
|
379
410
|
}
|
|
380
|
-
const committed = await commitHarnessVisibleChanges(deps.git, repoRoot, "
|
|
411
|
+
const committed = await commitHarnessVisibleChanges(deps.git, repoRoot, "[VCM Harness] Update fixed harness");
|
|
381
412
|
return {
|
|
382
413
|
...result,
|
|
383
414
|
changedFiles: committed.changedFiles.length > 0 ? committed.changedFiles : result.changedFiles,
|
|
@@ -401,7 +432,7 @@ export function createHarnessService(deps) {
|
|
|
401
432
|
if (changedFiles.length > 0) {
|
|
402
433
|
await bumpHarnessRevision(deps.fs, repoRoot, now());
|
|
403
434
|
}
|
|
404
|
-
const committed = await commitHarnessVisibleChanges(deps.git, repoRoot, "
|
|
435
|
+
const committed = await commitHarnessVisibleChanges(deps.git, repoRoot, "[VCM Harness] Update fixed harness");
|
|
405
436
|
return {
|
|
406
437
|
version: VCM_HARNESS_VERSION,
|
|
407
438
|
changedFiles: committed.changedFiles.length > 0 ? committed.changedFiles : changedFiles,
|
|
@@ -1444,7 +1475,8 @@ function ensureAgentAllowedTool(content, requiredTool) {
|
|
|
1444
1475
|
function normalizeAgentFrontmatter(content, definition) {
|
|
1445
1476
|
const allowedToolsUpdated = (definition.requiredTools ?? []).reduce(ensureAgentAllowedTool, content);
|
|
1446
1477
|
const disallowedToolsUpdated = ensureAgentDisallowedTools(allowedToolsUpdated, definition.requiredDisallowedTools);
|
|
1447
|
-
|
|
1478
|
+
const skillsUpdated = (definition.requiredSkills ?? []).reduce(ensureAgentSkill, disallowedToolsUpdated);
|
|
1479
|
+
return (definition.removedSkills ?? []).reduce(removeAgentSkill, skillsUpdated);
|
|
1448
1480
|
}
|
|
1449
1481
|
function ensureAgentSkill(content, requiredSkill) {
|
|
1450
1482
|
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
@@ -1466,6 +1498,30 @@ function ensureAgentSkill(content, requiredSkill) {
|
|
|
1466
1498
|
const nextSkills = `${skillsMatch[0].trimEnd()}\n - ${requiredSkill}`;
|
|
1467
1499
|
return content.replace(frontmatter, frontmatter.replace(skillsMatch[0], nextSkills));
|
|
1468
1500
|
}
|
|
1501
|
+
function removeAgentSkill(content, removedSkill) {
|
|
1502
|
+
const frontmatterMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---/);
|
|
1503
|
+
if (!frontmatterMatch) {
|
|
1504
|
+
return content;
|
|
1505
|
+
}
|
|
1506
|
+
const frontmatter = frontmatterMatch[0];
|
|
1507
|
+
const skillsMatch = frontmatter.match(/^skills:[ \t]*(?:\r?\n((?:\s+-\s+[^\r\n]+\r?\n?)*))?/m);
|
|
1508
|
+
if (!skillsMatch) {
|
|
1509
|
+
return content;
|
|
1510
|
+
}
|
|
1511
|
+
const listedSkills = (skillsMatch[1] ?? "")
|
|
1512
|
+
.split(/\r?\n/)
|
|
1513
|
+
.map((line) => line.match(/^\s+-\s+(.+)$/)?.[1]?.trim())
|
|
1514
|
+
.filter((skill) => Boolean(skill));
|
|
1515
|
+
const remainingSkills = listedSkills.filter((skill) => skill !== removedSkill);
|
|
1516
|
+
if (remainingSkills.length === listedSkills.length) {
|
|
1517
|
+
return content;
|
|
1518
|
+
}
|
|
1519
|
+
const lineEnding = skillsMatch[0].includes("\r\n") ? "\r\n" : "\n";
|
|
1520
|
+
const replacement = remainingSkills.length > 0
|
|
1521
|
+
? `skills:${lineEnding}${remainingSkills.map((skill) => ` - ${skill}`).join(lineEnding)}${lineEnding}`
|
|
1522
|
+
: "";
|
|
1523
|
+
return content.replace(frontmatter, frontmatter.replace(skillsMatch[0], replacement));
|
|
1524
|
+
}
|
|
1469
1525
|
function migrateLegacyHarnessFile(definition, currentContent, block) {
|
|
1470
1526
|
const legacyContent = definition.legacyWholeFile?.trimEnd();
|
|
1471
1527
|
if (!legacyContent) {
|
|
@@ -2,6 +2,19 @@ export const MEMORY_REVIEW_ROOT = ".ai/vcm/memory-review";
|
|
|
2
2
|
export const MEMORY_REVIEW_RUNS_ROOT = `${MEMORY_REVIEW_ROOT}/runs`;
|
|
3
3
|
export const MEMORY_REVIEW_STATE_PATH = `${MEMORY_REVIEW_ROOT}/state.json`;
|
|
4
4
|
export const ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH = `${MEMORY_REVIEW_ROOT}/candidates/architect/planning.md`;
|
|
5
|
+
const MEMORY_REVIEW_ROLE_DRAFT_PATTERN = new RegExp(`^${escapeRegExp(MEMORY_REVIEW_RUNS_ROOT)}/[A-Za-z0-9._-]+/drafts/([A-Za-z0-9._-]+)\\.md$`);
|
|
6
|
+
export function memoryReviewRoleDraftPath(runId, role) {
|
|
7
|
+
return `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/drafts/${role}.md`;
|
|
8
|
+
}
|
|
9
|
+
export function isMemoryProposalSubmissionPath(artifactPath, role) {
|
|
10
|
+
if (artifactPath === ARCHITECT_PLANNING_MEMORY_CANDIDATE_PATH) {
|
|
11
|
+
return role === "architect";
|
|
12
|
+
}
|
|
13
|
+
return MEMORY_REVIEW_ROLE_DRAFT_PATTERN.exec(artifactPath)?.[1] === role;
|
|
14
|
+
}
|
|
5
15
|
export function architectPlanningCandidateSnapshotPath(runId) {
|
|
6
16
|
return `${MEMORY_REVIEW_RUNS_ROOT}/${runId}/sources/architect-planning.md`;
|
|
7
17
|
}
|
|
18
|
+
function escapeRegExp(value) {
|
|
19
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20
|
+
}
|