vibe-coding-master 0.7.5 → 0.7.7
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 +40 -16
- package/dist/backend/adapters/git-adapter.js +15 -0
- package/dist/backend/api/artifact-routes.js +3 -0
- package/dist/backend/api/harness-routes.js +57 -27
- package/dist/backend/api/runtime-state-routes.js +7 -3
- package/dist/backend/api/task-routes.js +36 -4
- package/dist/backend/api/translation-routes.js +11 -2
- package/dist/backend/api/translation-worker-routes.js +37 -9
- package/dist/backend/cli/install-vcm-harness.js +40 -2
- package/dist/backend/gateway/gateway-service.js +34 -17
- package/dist/backend/server.js +12 -3
- package/dist/backend/services/artifact-service.js +4 -1
- package/dist/backend/services/auto-memory-service.js +156 -81
- package/dist/backend/services/claude-hook-service.js +50 -35
- package/dist/backend/services/command-dispatcher.js +1 -1
- package/dist/backend/services/gate-review-service.js +44 -0
- package/dist/backend/services/harness-feedback-service.js +47 -8
- package/dist/backend/services/harness-service.js +112 -34
- package/dist/backend/services/message-service.js +39 -2
- package/dist/backend/services/round-service.js +10 -121
- package/dist/backend/services/runtime-coordinator-service.js +18 -10
- package/dist/backend/services/runtime-recovery-service.js +1 -2
- package/dist/backend/services/session-service.js +36 -98
- package/dist/backend/services/status-service.js +1 -0
- package/dist/backend/services/task-close-service.js +12 -27
- package/dist/backend/services/task-workflow-service.js +228 -0
- package/dist/backend/services/translation-worker-service.js +14 -7
- package/dist/backend/templates/handoff.js +40 -1
- package/dist/backend/templates/harness/architect-agent.js +67 -19
- package/dist/backend/templates/harness/claude-root.js +25 -29
- package/dist/backend/templates/harness/gate-review.js +26 -13
- package/dist/backend/templates/harness/harness-engineer-agent.js +8 -8
- package/dist/backend/templates/harness/memory-block.js +69 -0
- package/dist/backend/templates/harness/project-known-issues.js +1 -0
- package/dist/backend/templates/harness/project-manager-agent.js +211 -73
- package/dist/backend/templates/harness/role-memory.js +9 -12
- package/dist/backend/templates/harness/tester-agent.js +4 -1
- package/dist/backend/templates/harness/vcm-architecture-interview-skill.js +82 -0
- package/dist/backend/templates/harness/vcm-final-acceptance-skill.js +4 -3
- package/dist/backend/templates/harness/vcm-harness-bootstrap-skill.js +14 -3
- package/dist/backend/templates/harness/vcm-propose-memory-skill.js +2 -2
- package/dist/backend/templates/harness/vcm-route-message-skill.js +5 -0
- package/dist/backend/templates/harness/vcm-task-state-skill.js +110 -0
- package/dist/backend/templates/message-envelope.js +1 -1
- package/dist/shared/constants.js +0 -10
- package/dist/shared/types/workflow.js +1 -0
- package/dist/shared/validation/artifact-check.js +20 -0
- package/dist-frontend/assets/index-42EpETgd.js +97 -0
- package/dist-frontend/assets/index-D65x2x0F.css +32 -0
- package/dist-frontend/index.html +2 -2
- package/package.json +1 -1
- package/scripts/harness-tools/check-durable-docs +298 -0
- package/scripts/verify-package.mjs +1 -0
- package/dist-frontend/assets/index-DCb-S6Ls.css +0 -32
- package/dist-frontend/assets/index-NTlycxx9.js +0 -97
|
@@ -16,6 +16,7 @@ const DEFAULT_REPORT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
|
16
16
|
const activeRuns = new Set();
|
|
17
17
|
const ARCHITECTURE_ANALYSIS_FIELDS = [
|
|
18
18
|
"Evidence Read",
|
|
19
|
+
"Architecture Brief Fit",
|
|
19
20
|
"End-To-End Flow",
|
|
20
21
|
"Scope Fit",
|
|
21
22
|
"Code Reality",
|
|
@@ -54,6 +55,7 @@ const CODE_DIFF_ANALYSIS_FIELDS = [
|
|
|
54
55
|
];
|
|
55
56
|
const SOURCE_ARTIFACTS = {
|
|
56
57
|
"architecture-plan": [
|
|
58
|
+
".ai/vcm/handoffs/architecture-brief.md",
|
|
57
59
|
".ai/vcm/handoffs/architecture-plan.md"
|
|
58
60
|
],
|
|
59
61
|
"validation-adequacy": [
|
|
@@ -201,6 +203,32 @@ export function createGateReviewService(deps) {
|
|
|
201
203
|
};
|
|
202
204
|
}
|
|
203
205
|
}
|
|
206
|
+
if (gate === "architecture-plan") {
|
|
207
|
+
const architectureBriefError = await readArchitectureBriefError(deps.fs, context.taskRepoRoot);
|
|
208
|
+
if (architectureBriefError) {
|
|
209
|
+
index = applyGateState(index, gate, {
|
|
210
|
+
status: "failed",
|
|
211
|
+
decision: undefined,
|
|
212
|
+
error: architectureBriefError,
|
|
213
|
+
exceptionReason: undefined,
|
|
214
|
+
requestId: undefined,
|
|
215
|
+
requestPath: undefined,
|
|
216
|
+
inputHash: undefined,
|
|
217
|
+
requestedAt: undefined,
|
|
218
|
+
startedAt: undefined,
|
|
219
|
+
completedAt: now(),
|
|
220
|
+
callbackStatus: "not_sent",
|
|
221
|
+
callbackError: undefined
|
|
222
|
+
}, now(), true);
|
|
223
|
+
await saveIndex(deps.fs, context.taskRepoRoot, index);
|
|
224
|
+
return {
|
|
225
|
+
status: "failed_to_start",
|
|
226
|
+
gate,
|
|
227
|
+
record: index.gates[gate],
|
|
228
|
+
message: architectureBriefError
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
204
232
|
const coreInput = await readCoreInputArtifact(deps.fs, context.taskRepoRoot, gate);
|
|
205
233
|
if (coreInput && coreInput.status !== "ready") {
|
|
206
234
|
index = applyGateState(index, gate, {
|
|
@@ -917,6 +945,22 @@ async function readCoreInputArtifact(fs, taskRepoRoot, gate) {
|
|
|
917
945
|
}
|
|
918
946
|
return { path: relativePath, status: "ready" };
|
|
919
947
|
}
|
|
948
|
+
async function readArchitectureBriefError(fs, taskRepoRoot) {
|
|
949
|
+
const relativePath = ".ai/vcm/handoffs/architecture-brief.md";
|
|
950
|
+
const absolutePath = resolveRepoPath(taskRepoRoot, relativePath);
|
|
951
|
+
if (!await fs.pathExists(absolutePath)) {
|
|
952
|
+
return `${relativePath} is missing. Complete Architect Interview before architecture planning.`;
|
|
953
|
+
}
|
|
954
|
+
const content = await fs.readText(absolutePath);
|
|
955
|
+
const check = checkMarkdownArtifact("architecture-brief", relativePath, content);
|
|
956
|
+
if (check.status !== "ok") {
|
|
957
|
+
return `${relativePath} is incomplete. Complete and confirm Architect Interview before requesting architecture-plan review.`;
|
|
958
|
+
}
|
|
959
|
+
if (!/^\s*Architecture Brief Status\s*:\s*confirmed\s*$/im.test(content)) {
|
|
960
|
+
return `${relativePath} is not confirmed. Obtain explicit user confirmation before architecture planning.`;
|
|
961
|
+
}
|
|
962
|
+
return undefined;
|
|
963
|
+
}
|
|
920
964
|
async function commandStdout(runner, cwd, args) {
|
|
921
965
|
const result = await runner.run("git", args, { cwd });
|
|
922
966
|
return result.exitCode === 0 ? result.stdout : "";
|
|
@@ -21,6 +21,23 @@ export function createHarnessFeedbackService(deps) {
|
|
|
21
21
|
warnings: []
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
+
async function sendPendingFeedback(repoRoot, input) {
|
|
25
|
+
await cleanupLegacyState(repoRoot);
|
|
26
|
+
const feedbackPath = input.feedbackPath.trim();
|
|
27
|
+
const pending = await listPendingFeedback(repoRoot);
|
|
28
|
+
const feedback = pending.find((item) => item.path === feedbackPath);
|
|
29
|
+
if (!feedback) {
|
|
30
|
+
throw new VcmError({
|
|
31
|
+
code: "HARNESS_FEEDBACK_NOT_PENDING",
|
|
32
|
+
message: "The selected Harness Feedback is no longer pending.",
|
|
33
|
+
statusCode: 404,
|
|
34
|
+
hint: "Refresh Harness Studio and select a feedback item that is still listed in the Inbox."
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
const session = await ensureIdleHarnessEngineer(repoRoot, input.taskSlug);
|
|
38
|
+
await submitTerminalInput(deps.runtime, session.id, buildPendingFeedbackPrompt(repoRoot, feedback.path));
|
|
39
|
+
return session;
|
|
40
|
+
}
|
|
24
41
|
async function startTaskRetrospective(repoRoot, input) {
|
|
25
42
|
await cleanupLegacyState(repoRoot);
|
|
26
43
|
const taskSlug = input.taskSlug.trim();
|
|
@@ -76,8 +93,8 @@ export function createHarnessFeedbackService(deps) {
|
|
|
76
93
|
async function assertHarnessEngineerAvailable(_repoRoot) {
|
|
77
94
|
return undefined;
|
|
78
95
|
}
|
|
79
|
-
async function
|
|
80
|
-
const existing = await deps.sessionService.
|
|
96
|
+
async function getIdleHarnessEngineer(repoRoot, taskSlug) {
|
|
97
|
+
const existing = await deps.sessionService.getRoleSession(repoRoot, taskSlug, "harness-engineer");
|
|
81
98
|
if (existing?.status === "running" && existing.activityStatus === "running") {
|
|
82
99
|
throw new VcmError({
|
|
83
100
|
code: "HARNESS_ENGINEER_BUSY",
|
|
@@ -86,12 +103,23 @@ export function createHarnessFeedbackService(deps) {
|
|
|
86
103
|
hint: "Wait for the current Harness Engineer turn to finish, then retry."
|
|
87
104
|
});
|
|
88
105
|
}
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
106
|
+
const input = { cols: 120, rows: 32 };
|
|
107
|
+
const session = existing?.status === "running"
|
|
108
|
+
? existing
|
|
109
|
+
: existing?.claudeSessionId
|
|
110
|
+
? await deps.sessionService.resumeRoleSession(repoRoot, taskSlug, "harness-engineer", input)
|
|
111
|
+
: await deps.sessionService.startRoleSession(repoRoot, taskSlug, "harness-engineer", input);
|
|
112
|
+
if (session.status !== "running" || session.activityStatus === "running") {
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
if (!deps.runtime.getSession(session.id)) {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
return session;
|
|
119
|
+
}
|
|
120
|
+
async function ensureIdleHarnessEngineer(repoRoot, taskSlug) {
|
|
121
|
+
const session = await getIdleHarnessEngineer(repoRoot, taskSlug);
|
|
122
|
+
if (!session) {
|
|
95
123
|
throw new VcmError({
|
|
96
124
|
code: "HARNESS_ENGINEER_BUSY",
|
|
97
125
|
message: "Harness Engineer is busy or unavailable.",
|
|
@@ -144,6 +172,16 @@ export function createHarnessFeedbackService(deps) {
|
|
|
144
172
|
"End your turn after writing the result."
|
|
145
173
|
].join("\n");
|
|
146
174
|
}
|
|
175
|
+
function buildPendingFeedbackPrompt(repoRoot, feedbackPath) {
|
|
176
|
+
return [
|
|
177
|
+
"[VCM Harness Feedback]",
|
|
178
|
+
"",
|
|
179
|
+
"Review this feedback:",
|
|
180
|
+
resolveRepoPath(repoRoot, feedbackPath),
|
|
181
|
+
"",
|
|
182
|
+
"Verify the issue against the current harness and project evidence. Report your findings and proposed changes to the user."
|
|
183
|
+
].join("\n");
|
|
184
|
+
}
|
|
147
185
|
async function loadTaskRetrospectiveMarker(repoRoot, taskSlug) {
|
|
148
186
|
const markerPath = resolveRepoPath(repoRoot, getTaskRetrospectiveMarkerPath(taskSlug));
|
|
149
187
|
if (!(await deps.fs.pathExists(markerPath))) {
|
|
@@ -174,6 +212,7 @@ export function createHarnessFeedbackService(deps) {
|
|
|
174
212
|
}
|
|
175
213
|
return {
|
|
176
214
|
getState,
|
|
215
|
+
sendPendingFeedback,
|
|
177
216
|
startTaskRetrospective,
|
|
178
217
|
assertHarnessEngineerAvailable
|
|
179
218
|
};
|
|
@@ -8,18 +8,21 @@ import { renderGateReviewerAgentRules, renderRequestGateReviewTool, renderTransl
|
|
|
8
8
|
import { renderHarnessEngineerHarnessRules } from "../templates/harness/harness-engineer-agent.js";
|
|
9
9
|
import { renderRootClaudeHarnessRules } from "../templates/harness/claude-root.js";
|
|
10
10
|
import { renderGitignoreHarnessRules } from "../templates/harness/gitignore.js";
|
|
11
|
+
import { ensureVcmMemoryBlock } from "../templates/harness/memory-block.js";
|
|
11
12
|
import { renderLegacyProjectCodingStandardsTemplate, renderProjectCodingStandardsProjectSection, renderProjectCodingStandardsRules } from "../templates/harness/project-coding-standards.js";
|
|
12
13
|
import { renderProjectGlossaryTemplate } from "../templates/harness/project-glossary.js";
|
|
13
14
|
import { renderLegacyProjectKnownIssuesTemplate, renderProjectKnownIssuesRules, renderProjectKnownIssuesSection } from "../templates/harness/project-known-issues.js";
|
|
14
15
|
import { renderProjectManagerHarnessRules } from "../templates/harness/project-manager-agent.js";
|
|
15
16
|
import { renderPullRequestTemplateHarnessRules } from "../templates/harness/pull-request-template.js";
|
|
16
17
|
import { renderTesterHarnessRules } from "../templates/harness/tester-agent.js";
|
|
18
|
+
import { renderVcmArchitectureInterviewSkillRules } from "../templates/harness/vcm-architecture-interview-skill.js";
|
|
17
19
|
import { renderVcmFinalAcceptanceSkillRules } from "../templates/harness/vcm-final-acceptance-skill.js";
|
|
18
20
|
import { renderVcmHarnessBootstrapSkillRules } from "../templates/harness/vcm-harness-bootstrap-skill.js";
|
|
19
21
|
import { renderVcmLongRunningValidationSkillRules } from "../templates/harness/vcm-long-running-validation-skill.js";
|
|
20
22
|
import { renderVcmProposeMemorySkillRules } from "../templates/harness/vcm-propose-memory-skill.js";
|
|
21
23
|
import { renderVcmReportHarnessIssueSkillRules } from "../templates/harness/vcm-report-harness-issue-skill.js";
|
|
22
24
|
import { renderVcmRouteMessageSkillRules } from "../templates/harness/vcm-route-message-skill.js";
|
|
25
|
+
import { renderUpdateTaskStateTool, renderVcmTaskStateSkillRules } from "../templates/harness/vcm-task-state-skill.js";
|
|
23
26
|
import { submitTerminalInput } from "../runtime/terminal-submit.js";
|
|
24
27
|
import { VcmError } from "../errors.js";
|
|
25
28
|
import { bumpHarnessRevision, readHarnessRevisionState } from "./harness-revision.js";
|
|
@@ -57,6 +60,7 @@ const HARNESS_FILES = [
|
|
|
57
60
|
path: "CLAUDE.md",
|
|
58
61
|
title: "CLAUDE.md",
|
|
59
62
|
blankLineBeforeEnd: true,
|
|
63
|
+
memoryBlock: true,
|
|
60
64
|
renderRules: renderRootClaudeHarnessRules
|
|
61
65
|
},
|
|
62
66
|
{
|
|
@@ -95,6 +99,14 @@ const HARNESS_FILES = [
|
|
|
95
99
|
title: "Pull Request Template",
|
|
96
100
|
renderRules: renderPullRequestTemplateHarnessRules
|
|
97
101
|
},
|
|
102
|
+
{
|
|
103
|
+
kind: "skill-vcm-architecture-interview",
|
|
104
|
+
path: ".claude/skills/vcm-architecture-interview/SKILL.md",
|
|
105
|
+
title: "VCM Architecture Interview Skill",
|
|
106
|
+
frontmatter: renderSkillFrontmatter("vcm-architecture-interview", "Use when Architect must confirm user-owned behavior and contract decisions before architecture planning."),
|
|
107
|
+
ownership: "whole-file",
|
|
108
|
+
renderRules: renderVcmArchitectureInterviewSkillRules
|
|
109
|
+
},
|
|
98
110
|
{
|
|
99
111
|
kind: "skill-vcm-route-message",
|
|
100
112
|
path: ".claude/skills/vcm-route-message/SKILL.md",
|
|
@@ -103,6 +115,14 @@ const HARNESS_FILES = [
|
|
|
103
115
|
ownership: "whole-file",
|
|
104
116
|
renderRules: renderVcmRouteMessageSkillRules
|
|
105
117
|
},
|
|
118
|
+
{
|
|
119
|
+
kind: "skill-vcm-task-state",
|
|
120
|
+
path: ".claude/skills/vcm-task-state/SKILL.md",
|
|
121
|
+
title: "VCM Task State Skill",
|
|
122
|
+
frontmatter: renderSkillFrontmatter("vcm-task-state", "Use only as project-manager to declare the current task workflow checkpoint to VCM."),
|
|
123
|
+
ownership: "whole-file",
|
|
124
|
+
renderRules: renderVcmTaskStateSkillRules
|
|
125
|
+
},
|
|
106
126
|
{
|
|
107
127
|
kind: "skill-vcm-final-acceptance",
|
|
108
128
|
path: ".claude/skills/vcm-final-acceptance/SKILL.md",
|
|
@@ -155,6 +175,7 @@ const HARNESS_FILES = [
|
|
|
155
175
|
kind: "agent-gate-reviewer",
|
|
156
176
|
path: ".claude/agents/gate-reviewer.md",
|
|
157
177
|
title: "Gate Reviewer Agent",
|
|
178
|
+
memoryBlock: true,
|
|
158
179
|
frontmatter: renderAgentFrontmatter("gate-reviewer", "VCM independent gate review role for architecture plans, validation adequacy, and code diffs.", { tools: "Read, Grep, Glob, Bash, Write" }),
|
|
159
180
|
renderRules: renderGateReviewerAgentRules
|
|
160
181
|
},
|
|
@@ -162,14 +183,15 @@ const HARNESS_FILES = [
|
|
|
162
183
|
kind: "agent-translator",
|
|
163
184
|
path: ".claude/agents/translator.md",
|
|
164
185
|
title: "Translator Agent",
|
|
165
|
-
frontmatter: renderAgentFrontmatter("translator", "VCM
|
|
186
|
+
frontmatter: renderAgentFrontmatter("translator", "VCM task-scoped translation tool role for conversation translation, file translation, bootstrap, and memory updates."),
|
|
166
187
|
renderRules: renderTranslatorAgentRules
|
|
167
188
|
},
|
|
168
189
|
{
|
|
169
190
|
kind: "agent-harness-engineer",
|
|
170
191
|
path: ".claude/agents/harness-engineer.md",
|
|
171
192
|
title: "Harness Engineer Agent",
|
|
172
|
-
|
|
193
|
+
memoryBlock: true,
|
|
194
|
+
frontmatter: renderAgentFrontmatter("harness-engineer", "VCM task-scoped harness maintenance role for harness diagnosis, diff proposals, and VCM issue drafts."),
|
|
173
195
|
renderRules: renderHarnessEngineerHarnessRules
|
|
174
196
|
},
|
|
175
197
|
{
|
|
@@ -186,10 +208,18 @@ const HARNESS_FILES = [
|
|
|
186
208
|
ownership: "raw-file",
|
|
187
209
|
renderRules: renderRequestGateReviewTool
|
|
188
210
|
},
|
|
211
|
+
{
|
|
212
|
+
kind: "tool-update-task-state",
|
|
213
|
+
path: ".ai/tools/update-task-state",
|
|
214
|
+
title: "Update Task State Tool",
|
|
215
|
+
ownership: "raw-file",
|
|
216
|
+
renderRules: renderUpdateTaskStateTool
|
|
217
|
+
},
|
|
189
218
|
{
|
|
190
219
|
kind: "agent-project-manager",
|
|
191
220
|
path: ".claude/agents/project-manager.md",
|
|
192
221
|
title: "Project Manager Agent",
|
|
222
|
+
memoryBlock: true,
|
|
193
223
|
frontmatter: renderAgentFrontmatter("project-manager", "User-facing VCM orchestration role for task clarification, role routing, handoffs, acceptance, and PR preparation."),
|
|
194
224
|
renderRules: renderProjectManagerHarnessRules
|
|
195
225
|
},
|
|
@@ -197,6 +227,7 @@ const HARNESS_FILES = [
|
|
|
197
227
|
kind: "agent-architect",
|
|
198
228
|
path: ".claude/agents/architect.md",
|
|
199
229
|
title: "Architect Agent",
|
|
230
|
+
memoryBlock: true,
|
|
200
231
|
blankLineBeforeEnd: true,
|
|
201
232
|
frontmatter: renderAgentFrontmatter("architect", "VCM architecture role for plans, module boundaries, public contracts, verifiable behavior, and docs sync."),
|
|
202
233
|
renderRules: renderArchitectHarnessRules
|
|
@@ -205,6 +236,7 @@ const HARNESS_FILES = [
|
|
|
205
236
|
kind: "agent-coder",
|
|
206
237
|
path: ".claude/agents/coder.md",
|
|
207
238
|
title: "Coder Agent",
|
|
239
|
+
memoryBlock: true,
|
|
208
240
|
frontmatter: renderAgentFrontmatter("coder", "VCM implementation role for scoped code changes and focused tests.", { tools: "Read, Grep, Glob, Bash, Edit, Write, Agent" }),
|
|
209
241
|
renderRules: renderCoderHarnessRules
|
|
210
242
|
},
|
|
@@ -212,6 +244,7 @@ const HARNESS_FILES = [
|
|
|
212
244
|
kind: "agent-tester",
|
|
213
245
|
path: ".claude/agents/tester.md",
|
|
214
246
|
title: "Tester Agent",
|
|
247
|
+
memoryBlock: true,
|
|
215
248
|
frontmatter: renderAgentFrontmatter("tester", "VCM testing role for validation, test adequacy, approved-scope validation, and risk findings."),
|
|
216
249
|
renderRules: renderTesterHarnessRules
|
|
217
250
|
}
|
|
@@ -316,12 +349,12 @@ export function createHarnessService(deps) {
|
|
|
316
349
|
async mergeRepositoryDiffToCurrentBranch(baseRepoRoot, input) {
|
|
317
350
|
return mergeRepositoryDiffToCurrentBranch(requireRepositoryMergeGit(deps.git), baseRepoRoot, input, now());
|
|
318
351
|
},
|
|
319
|
-
async getBootstrapStatus(repoRoot, targetRepoRoot = repoRoot) {
|
|
320
|
-
return getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion);
|
|
352
|
+
async getBootstrapStatus(repoRoot, targetRepoRoot = repoRoot, taskSlug) {
|
|
353
|
+
return getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, taskSlug);
|
|
321
354
|
},
|
|
322
355
|
async startHarnessBootstrap(repoRoot, targetRepoRoot = repoRoot, input = {}) {
|
|
323
356
|
const session = await ensureHarnessEngineerForBootstrap(deps, repoRoot, targetRepoRoot, now, vcmVersion, input);
|
|
324
|
-
const nextStatus = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion);
|
|
357
|
+
const nextStatus = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, input.taskSlug);
|
|
325
358
|
return {
|
|
326
359
|
status: {
|
|
327
360
|
...nextStatus,
|
|
@@ -333,7 +366,7 @@ export function createHarnessService(deps) {
|
|
|
333
366
|
},
|
|
334
367
|
async restartHarnessBootstrap(repoRoot, targetRepoRoot = repoRoot, input = {}) {
|
|
335
368
|
const session = await restartHarnessEngineerForBootstrap(deps, repoRoot, targetRepoRoot, now, vcmVersion, input);
|
|
336
|
-
const nextStatus = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion);
|
|
369
|
+
const nextStatus = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, input.taskSlug);
|
|
337
370
|
return {
|
|
338
371
|
status: {
|
|
339
372
|
...nextStatus,
|
|
@@ -343,8 +376,8 @@ export function createHarnessService(deps) {
|
|
|
343
376
|
prompt: buildHarnessBootstrapPrompt(repoRoot, targetRepoRoot)
|
|
344
377
|
};
|
|
345
378
|
},
|
|
346
|
-
async stopHarnessBootstrap(repoRoot) {
|
|
347
|
-
const existing = await getCurrentHarnessEngineerBootstrapSession(deps, repoRoot);
|
|
379
|
+
async stopHarnessBootstrap(repoRoot, targetRepoRoot = repoRoot, taskSlug) {
|
|
380
|
+
const existing = await getCurrentHarnessEngineerBootstrapSession(deps, repoRoot, taskSlug);
|
|
348
381
|
if (!existing) {
|
|
349
382
|
throw new VcmError({
|
|
350
383
|
code: "HARNESS_BOOTSTRAP_SESSION_MISSING",
|
|
@@ -352,11 +385,13 @@ export function createHarnessService(deps) {
|
|
|
352
385
|
statusCode: 404
|
|
353
386
|
});
|
|
354
387
|
}
|
|
355
|
-
|
|
388
|
+
if (taskSlug) {
|
|
389
|
+
await deps.harnessEngineerSessions?.stopRoleSession(repoRoot, taskSlug, "harness-engineer");
|
|
390
|
+
}
|
|
356
391
|
await clearHarnessBootstrapRunState(deps.fs, repoRoot);
|
|
357
|
-
return getHarnessBootstrapStatus(deps, repoRoot,
|
|
392
|
+
return getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, taskSlug);
|
|
358
393
|
},
|
|
359
|
-
async runHarnessBootstrap(repoRoot, targetRepoRoot = repoRoot) {
|
|
394
|
+
async runHarnessBootstrap(repoRoot, targetRepoRoot = repoRoot, taskSlug) {
|
|
360
395
|
if (!deps.runtime || !deps.harnessEngineerSessions) {
|
|
361
396
|
throw new VcmError({
|
|
362
397
|
code: "HARNESS_BOOTSTRAP_UNAVAILABLE",
|
|
@@ -365,7 +400,7 @@ export function createHarnessService(deps) {
|
|
|
365
400
|
});
|
|
366
401
|
}
|
|
367
402
|
await assertHarnessWorktreeClean(deps.git, targetRepoRoot);
|
|
368
|
-
const status = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion);
|
|
403
|
+
const status = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, taskSlug);
|
|
369
404
|
const session = status.session;
|
|
370
405
|
if (!session || session.status !== "running" || !deps.runtime.getSession(session.id)) {
|
|
371
406
|
throw new VcmError({
|
|
@@ -379,6 +414,7 @@ export function createHarnessService(deps) {
|
|
|
379
414
|
await persistHarnessBootstrapRunState(deps.fs, repoRoot, {
|
|
380
415
|
version: 1,
|
|
381
416
|
status: "running",
|
|
417
|
+
taskSlug,
|
|
382
418
|
targetRepoRoot,
|
|
383
419
|
sessionId: session.id,
|
|
384
420
|
claudeSessionId: session.claudeSessionId,
|
|
@@ -386,7 +422,7 @@ export function createHarnessService(deps) {
|
|
|
386
422
|
updatedAt: timestamp
|
|
387
423
|
});
|
|
388
424
|
await submitTerminalInput(deps.runtime, session.id, prompt);
|
|
389
|
-
const nextStatus = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion);
|
|
425
|
+
const nextStatus = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, taskSlug);
|
|
390
426
|
return {
|
|
391
427
|
status: {
|
|
392
428
|
...nextStatus,
|
|
@@ -400,7 +436,7 @@ export function createHarnessService(deps) {
|
|
|
400
436
|
async recordHarnessBootstrapHook(repoRoot, input) {
|
|
401
437
|
const state = await loadPersistedHarnessBootstrapRunState(deps.fs, repoRoot);
|
|
402
438
|
if (state?.status !== "running" || !matchesBootstrapRunState(state, input)) {
|
|
403
|
-
return getHarnessBootstrapStatus(deps, repoRoot, state?.targetRepoRoot ?? repoRoot, now, vcmVersion);
|
|
439
|
+
return getHarnessBootstrapStatus(deps, repoRoot, state?.targetRepoRoot ?? repoRoot, now, vcmVersion, state?.taskSlug);
|
|
404
440
|
}
|
|
405
441
|
const timestamp = now();
|
|
406
442
|
if (input.eventName === "Stop" && state.targetRepoRoot) {
|
|
@@ -413,7 +449,7 @@ export function createHarnessService(deps) {
|
|
|
413
449
|
updatedAt: timestamp,
|
|
414
450
|
lastHookEvent: input.eventName
|
|
415
451
|
});
|
|
416
|
-
return getHarnessBootstrapStatus(deps, repoRoot, state.targetRepoRoot ?? repoRoot, now, vcmVersion);
|
|
452
|
+
return getHarnessBootstrapStatus(deps, repoRoot, state.targetRepoRoot ?? repoRoot, now, vcmVersion, state.taskSlug);
|
|
417
453
|
}
|
|
418
454
|
};
|
|
419
455
|
}
|
|
@@ -425,7 +461,8 @@ async function ensureHarnessEngineerForBootstrap(deps, repoRoot, targetRepoRoot,
|
|
|
425
461
|
statusCode: 501
|
|
426
462
|
});
|
|
427
463
|
}
|
|
428
|
-
const
|
|
464
|
+
const taskSlug = requireHarnessEngineerTaskSlug(input.taskSlug);
|
|
465
|
+
const currentStatus = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, taskSlug);
|
|
429
466
|
if (currentStatus.session?.status === "running") {
|
|
430
467
|
return currentStatus.session;
|
|
431
468
|
}
|
|
@@ -436,7 +473,14 @@ async function ensureHarnessEngineerForBootstrap(deps, repoRoot, targetRepoRoot,
|
|
|
436
473
|
statusCode: 409
|
|
437
474
|
});
|
|
438
475
|
}
|
|
439
|
-
|
|
476
|
+
const existing = await deps.harnessEngineerSessions.getRoleSession(repoRoot, taskSlug, "harness-engineer");
|
|
477
|
+
if (existing?.status === "running") {
|
|
478
|
+
return toHarnessBootstrapSession(existing);
|
|
479
|
+
}
|
|
480
|
+
if (existing?.claudeSessionId) {
|
|
481
|
+
return toHarnessBootstrapSession(await deps.harnessEngineerSessions.resumeRoleSession(repoRoot, taskSlug, "harness-engineer", input));
|
|
482
|
+
}
|
|
483
|
+
return toHarnessBootstrapSession(await deps.harnessEngineerSessions.startRoleSession(repoRoot, taskSlug, "harness-engineer", input));
|
|
440
484
|
}
|
|
441
485
|
async function restartHarnessEngineerForBootstrap(deps, repoRoot, targetRepoRoot, now, vcmVersion, input) {
|
|
442
486
|
if (!deps.harnessEngineerSessions) {
|
|
@@ -446,7 +490,8 @@ async function restartHarnessEngineerForBootstrap(deps, repoRoot, targetRepoRoot
|
|
|
446
490
|
statusCode: 501
|
|
447
491
|
});
|
|
448
492
|
}
|
|
449
|
-
const
|
|
493
|
+
const taskSlug = requireHarnessEngineerTaskSlug(input.taskSlug);
|
|
494
|
+
const currentStatus = await getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, taskSlug);
|
|
450
495
|
if (!currentStatus.canStart) {
|
|
451
496
|
throw new VcmError({
|
|
452
497
|
code: "HARNESS_BOOTSTRAP_NOT_READY",
|
|
@@ -454,12 +499,27 @@ async function restartHarnessEngineerForBootstrap(deps, repoRoot, targetRepoRoot
|
|
|
454
499
|
statusCode: 409
|
|
455
500
|
});
|
|
456
501
|
}
|
|
457
|
-
return toHarnessBootstrapSession(await deps.harnessEngineerSessions.
|
|
502
|
+
return toHarnessBootstrapSession(await deps.harnessEngineerSessions.restartRoleSession(repoRoot, taskSlug, "harness-engineer", input));
|
|
458
503
|
}
|
|
459
|
-
async function getCurrentHarnessEngineerBootstrapSession(deps, repoRoot) {
|
|
460
|
-
|
|
504
|
+
async function getCurrentHarnessEngineerBootstrapSession(deps, repoRoot, taskSlug) {
|
|
505
|
+
if (!taskSlug) {
|
|
506
|
+
return undefined;
|
|
507
|
+
}
|
|
508
|
+
const session = await deps.harnessEngineerSessions?.getRoleSession(repoRoot, taskSlug, "harness-engineer");
|
|
461
509
|
return session ? toHarnessBootstrapSession(session) : undefined;
|
|
462
510
|
}
|
|
511
|
+
function requireHarnessEngineerTaskSlug(value) {
|
|
512
|
+
const taskSlug = value?.trim();
|
|
513
|
+
if (!taskSlug) {
|
|
514
|
+
throw new VcmError({
|
|
515
|
+
code: "HARNESS_ENGINEER_TASK_REQUIRED",
|
|
516
|
+
message: "Harness Engineer requires an active task.",
|
|
517
|
+
statusCode: 409,
|
|
518
|
+
hint: "Create or select a task before using Harness Engineer."
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
return taskSlug;
|
|
522
|
+
}
|
|
463
523
|
function toHarnessBootstrapSession(session) {
|
|
464
524
|
return {
|
|
465
525
|
id: session.id,
|
|
@@ -1059,6 +1119,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1059
1119
|
: getManagedBlockPattern(definition);
|
|
1060
1120
|
const exists = await fs.pathExists(absolutePath);
|
|
1061
1121
|
if (!exists) {
|
|
1122
|
+
const newContent = expectedContent ?? renderNewHarnessFile(definition, expectedBlock ?? "");
|
|
1062
1123
|
return {
|
|
1063
1124
|
definition,
|
|
1064
1125
|
status: {
|
|
@@ -1073,7 +1134,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1073
1134
|
action: "create",
|
|
1074
1135
|
reason: "File is missing; VCM will create a recommended default."
|
|
1075
1136
|
},
|
|
1076
|
-
nextContent:
|
|
1137
|
+
nextContent: definition.memoryBlock ? ensureVcmMemoryBlock(newContent) : newContent
|
|
1077
1138
|
};
|
|
1078
1139
|
}
|
|
1079
1140
|
const currentContent = await fs.readText(absolutePath);
|
|
@@ -1105,6 +1166,7 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1105
1166
|
if (!match) {
|
|
1106
1167
|
const migratedContent = migrateLegacyHarnessFile(definition, currentContent, expectedBlock);
|
|
1107
1168
|
if (migratedContent) {
|
|
1169
|
+
const nextContent = definition.memoryBlock ? ensureVcmMemoryBlock(migratedContent) : migratedContent;
|
|
1108
1170
|
return {
|
|
1109
1171
|
definition,
|
|
1110
1172
|
status: {
|
|
@@ -1119,9 +1181,10 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1119
1181
|
action: "update",
|
|
1120
1182
|
reason: "Legacy VCM whole-file baseline will be migrated to a managed block."
|
|
1121
1183
|
},
|
|
1122
|
-
nextContent
|
|
1184
|
+
nextContent
|
|
1123
1185
|
};
|
|
1124
1186
|
}
|
|
1187
|
+
const insertedContent = `${currentContent.trimEnd()}\n\n${expectedBlock}\n`;
|
|
1125
1188
|
return {
|
|
1126
1189
|
definition,
|
|
1127
1190
|
status: {
|
|
@@ -1136,12 +1199,14 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1136
1199
|
action: "insert",
|
|
1137
1200
|
reason: "File exists but does not contain VCM managed rules."
|
|
1138
1201
|
},
|
|
1139
|
-
nextContent:
|
|
1202
|
+
nextContent: definition.memoryBlock ? ensureVcmMemoryBlock(insertedContent) : insertedContent
|
|
1140
1203
|
};
|
|
1141
1204
|
}
|
|
1142
1205
|
const managedVersion = match[1] ? Number(match[1]) : undefined;
|
|
1143
1206
|
const currentBlock = match[0];
|
|
1144
|
-
const
|
|
1207
|
+
const blockUpdatedContent = currentContent.replace(managedBlockPattern, expectedBlock);
|
|
1208
|
+
const nextContent = definition.memoryBlock ? ensureVcmMemoryBlock(blockUpdatedContent) : blockUpdatedContent;
|
|
1209
|
+
const action = currentContent === nextContent ? "ok" : "update";
|
|
1145
1210
|
return {
|
|
1146
1211
|
definition,
|
|
1147
1212
|
status: {
|
|
@@ -1157,13 +1222,15 @@ async function analyzeHarnessFile(fs, repoRoot, definition) {
|
|
|
1157
1222
|
: {
|
|
1158
1223
|
path: definition.path,
|
|
1159
1224
|
action,
|
|
1160
|
-
reason:
|
|
1161
|
-
? "VCM
|
|
1162
|
-
:
|
|
1225
|
+
reason: currentBlock === expectedBlock && definition.memoryBlock
|
|
1226
|
+
? "VCM memory block is missing."
|
|
1227
|
+
: managedVersion === VCM_HARNESS_VERSION
|
|
1228
|
+
? "VCM managed rules differ from the current recommended template."
|
|
1229
|
+
: `VCM managed block version is ${managedVersion ?? "missing"}; current version is ${VCM_HARNESS_VERSION}.`
|
|
1163
1230
|
},
|
|
1164
1231
|
nextContent: action === "ok"
|
|
1165
1232
|
? undefined
|
|
1166
|
-
:
|
|
1233
|
+
: nextContent
|
|
1167
1234
|
};
|
|
1168
1235
|
}
|
|
1169
1236
|
async function analyzeLegacyCodexHarnessPaths(fs, repoRoot) {
|
|
@@ -1417,7 +1484,7 @@ async function analyzeHarnessManifest(fs, repoRoot, vcmVersion) {
|
|
|
1417
1484
|
}
|
|
1418
1485
|
return undefined;
|
|
1419
1486
|
}
|
|
1420
|
-
async function getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion) {
|
|
1487
|
+
async function getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vcmVersion, taskSlug) {
|
|
1421
1488
|
const moduleIndex = await readOptionalJsonObject(deps.fs, targetRepoRoot, ".ai/generated/module-index.json");
|
|
1422
1489
|
const checks = [
|
|
1423
1490
|
await checkFixedHarness(deps.fs, targetRepoRoot, vcmVersion),
|
|
@@ -1430,8 +1497,11 @@ async function getHarnessBootstrapStatus(deps, repoRoot, targetRepoRoot, now, vc
|
|
|
1430
1497
|
await checkModuleArchitectureDocs(deps.fs, targetRepoRoot, moduleIndex),
|
|
1431
1498
|
await checkFilledMarkdown(deps.fs, targetRepoRoot, "docs/TESTING.md", "Testing doc", "testing-doc")
|
|
1432
1499
|
];
|
|
1433
|
-
const
|
|
1434
|
-
const
|
|
1500
|
+
const persistedRunState = await loadPersistedHarnessBootstrapRunState(deps.fs, repoRoot);
|
|
1501
|
+
const runState = taskSlug && persistedRunState?.taskSlug !== taskSlug
|
|
1502
|
+
? undefined
|
|
1503
|
+
: persistedRunState;
|
|
1504
|
+
const session = await getCurrentHarnessEngineerBootstrapSession(deps, repoRoot, taskSlug);
|
|
1435
1505
|
const fixedHarnessReady = checks[0]?.status === "ok";
|
|
1436
1506
|
const projectChecks = checks.slice(1);
|
|
1437
1507
|
const projectComplete = projectChecks.every((check) => check.status === "ok");
|
|
@@ -1461,6 +1531,7 @@ async function checkFixedHarness(fs, repoRoot, vcmVersion) {
|
|
|
1461
1531
|
".claude/skills/vcm-harness-bootstrap/SKILL.md",
|
|
1462
1532
|
"docs/GLOSSARY.md",
|
|
1463
1533
|
"docs/CODING_STANDARDS.md",
|
|
1534
|
+
".ai/tools/check-durable-docs",
|
|
1464
1535
|
".ai/tools/generate-module-index",
|
|
1465
1536
|
".ai/tools/generate-public-surface"
|
|
1466
1537
|
];
|
|
@@ -1680,6 +1751,8 @@ async function loadPersistedHarnessBootstrapRunState(fs, repoRoot) {
|
|
|
1680
1751
|
return {
|
|
1681
1752
|
version: 1,
|
|
1682
1753
|
status,
|
|
1754
|
+
taskSlug: typeof payload.taskSlug === "string" ? payload.taskSlug : undefined,
|
|
1755
|
+
targetRepoRoot: typeof payload.targetRepoRoot === "string" ? payload.targetRepoRoot : undefined,
|
|
1683
1756
|
sessionId: typeof payload.sessionId === "string" ? payload.sessionId : undefined,
|
|
1684
1757
|
claudeSessionId: typeof payload.claudeSessionId === "string" ? payload.claudeSessionId : undefined,
|
|
1685
1758
|
startedAt: typeof payload.startedAt === "string" ? payload.startedAt : undefined,
|
|
@@ -1746,7 +1819,9 @@ function bootstrapWarnings(status, checks, session, runState) {
|
|
|
1746
1819
|
return warnings;
|
|
1747
1820
|
}
|
|
1748
1821
|
function buildHarnessBootstrapPrompt(baseRepoRoot, targetRepoRoot) {
|
|
1749
|
-
return `
|
|
1822
|
+
return `[VCM HARNESS BOOTSTRAP]
|
|
1823
|
+
|
|
1824
|
+
Use the vcm-harness-bootstrap skill to finish the VCM harness bootstrap for the active task worktree.
|
|
1750
1825
|
|
|
1751
1826
|
Base repository root:
|
|
1752
1827
|
${baseRepoRoot}
|
|
@@ -1764,6 +1839,7 @@ Required work:
|
|
|
1764
1839
|
- Fill target docs/ARCHITECTURE.md with project-level module overview, responsibilities, relationships, dependency direction, project-wide constraints, and links to module-level architecture docs.
|
|
1765
1840
|
- Create or update target module-level ARCHITECTURE.md files for clear non-root module boundaries with architectureDoc paths in module-index.json.
|
|
1766
1841
|
- Fill target docs/TESTING.md with project-native validation levels, commands, validation selection rules, final-validation cleanup, test layout, integration/E2E case lists, generated-context freshness checks, and known testing gaps.
|
|
1842
|
+
- Run .ai/tools/check-durable-docs and correct every bootstrap-owned finding.
|
|
1767
1843
|
- Review git status and git diff in the target task worktree.
|
|
1768
1844
|
- Stage only allowed bootstrap harness changes and create a commit in the target task worktree.
|
|
1769
1845
|
|
|
@@ -1775,7 +1851,9 @@ Boundaries:
|
|
|
1775
1851
|
- VCM will not create the bootstrap commit for you.
|
|
1776
1852
|
|
|
1777
1853
|
Final response:
|
|
1778
|
-
Summarize files reviewed, files updated, generated artifacts, commit hash, final git status, verified claims, inferred claims, unknowns, confirmation-needed items, and suggested validation commands
|
|
1854
|
+
Summarize files reviewed, files updated, generated artifacts, durable-doc audit result, commit hash, final git status, verified claims, inferred claims, unknowns, confirmation-needed items, and suggested validation commands.
|
|
1855
|
+
|
|
1856
|
+
[/VCM HARNESS BOOTSTRAP]`;
|
|
1779
1857
|
}
|
|
1780
1858
|
export function createScriptFixedHarnessInstaller(scriptPath = path.resolve(process.cwd(), "scripts/install-vcm-harness.mjs")) {
|
|
1781
1859
|
return async (repoRoot) => {
|