coding-agent-harness 1.0.1 → 1.0.2
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 +19 -0
- package/README.en-US.md +14 -0
- package/README.md +111 -86
- package/README.zh-CN.md +270 -0
- package/SKILL.md +116 -189
- package/docs-release/README.md +72 -5
- package/docs-release/architecture/overview.md +286 -28
- package/docs-release/architecture/overview.zh-CN.md +288 -0
- package/docs-release/assets/dashboard-overview-en.png +0 -0
- package/docs-release/assets/harness-architecture.svg +163 -0
- package/docs-release/assets/harness-workflow.svg +64 -0
- package/docs-release/guides/agent-installation.en-US.md +214 -0
- package/docs-release/guides/agent-installation.md +123 -26
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +112 -0
- package/docs-release/guides/document-audience-and-surfaces.md +112 -0
- package/docs-release/guides/full-legacy-migration-subagent-strategy.md +334 -0
- package/docs-release/guides/full-legacy-migration-subagent-strategy.zh-CN.md +334 -0
- package/docs-release/guides/legacy-migration-agent-prompt.md +384 -0
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +361 -0
- package/docs-release/guides/migration-playbook.en-US.md +325 -0
- package/docs-release/guides/migration-playbook.md +329 -0
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +252 -0
- package/docs-release/guides/parent-control-repository-pattern.md +252 -0
- package/docs-release/guides/repository-operating-models.en-US.md +196 -0
- package/docs-release/guides/repository-operating-models.md +196 -0
- package/docs-release/intl/README.md +15 -0
- package/docs-release/intl/de-DE.md +18 -0
- package/docs-release/intl/en-US.md +18 -0
- package/docs-release/intl/es-ES.md +18 -0
- package/docs-release/intl/fr-FR.md +18 -0
- package/docs-release/intl/ja-JP.md +18 -0
- package/docs-release/intl/ko-KR.md +18 -0
- package/docs-release/intl/zh-CN.md +18 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/brief.md +13 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/lesson_candidates.md +24 -0
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +1 -1
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/task_plan.md +4 -2
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/{visual_roadmap.md → visual_map.md} +9 -1
- package/package.json +3 -1
- package/references/agents-md-pattern.md +3 -3
- package/references/docs-directory-standard.md +47 -3
- package/references/external-source-intake-standard.md +75 -0
- package/references/harness-ledger.md +5 -3
- package/references/legacy-12-phase-bootstrap.md +41 -0
- package/references/lessons-governance.md +23 -6
- package/references/planning-loop.md +41 -3
- package/references/project-onboarding-audit.md +10 -0
- package/references/repo-governance-standard.md +2 -0
- package/references/testing-standard.md +50 -0
- package/references/walkthrough-closeout.md +6 -5
- package/scripts/check-harness.mjs +76 -35
- package/scripts/harness.mjs +303 -12
- package/scripts/lib/capability-registry.mjs +533 -0
- package/scripts/lib/check-profiles.mjs +510 -0
- package/scripts/lib/core-shared.mjs +186 -0
- package/scripts/lib/dashboard-data.mjs +389 -0
- package/scripts/lib/dashboard-workbench.mjs +217 -0
- package/scripts/lib/dashboard-writer.mjs +93 -2
- package/scripts/lib/harness-core.mjs +10 -1318
- package/scripts/lib/lesson-maintenance.mjs +145 -0
- package/scripts/lib/markdown-utils.mjs +158 -0
- package/scripts/lib/migration-planner.mjs +478 -0
- package/scripts/lib/migration-support.mjs +312 -0
- package/scripts/lib/task-lifecycle.mjs +755 -0
- package/scripts/lib/task-scanner.mjs +682 -0
- package/scripts/smoke-dashboard.mjs +22 -0
- package/scripts/test-harness.mjs +926 -14
- package/templates/AGENTS.md.template +41 -30
- package/templates/architecture/Architecture-SSoT.md +21 -0
- package/templates/architecture/README.md +49 -0
- package/templates/architecture/critical-flows.md +22 -0
- package/templates/architecture/local-repo-context.md +20 -0
- package/templates/architecture/service-catalog.md +17 -0
- package/templates/architecture/services/service-template.md +31 -0
- package/templates/architecture/system-map.md +22 -0
- package/templates/dashboard/assets/app-src/00-state.js +41 -0
- package/templates/dashboard/assets/app-src/10-router.js +76 -0
- package/templates/dashboard/assets/app-src/20-overview.js +235 -0
- package/templates/dashboard/assets/app-src/30-tasks.js +563 -0
- package/templates/dashboard/assets/app-src/40-modules.js +58 -0
- package/templates/dashboard/assets/app-src/45-review.js +128 -0
- package/templates/dashboard/assets/app-src/50-migration.js +169 -0
- package/templates/dashboard/assets/app-src/60-shared.js +61 -0
- package/templates/dashboard/assets/app-src/90-bindings.js +382 -0
- package/templates/dashboard/assets/app.css +2575 -310
- package/templates/dashboard/assets/app.js +1498 -307
- package/templates/dashboard/assets/app.manifest.json +11 -0
- package/templates/dashboard/assets/i18n.js +429 -44
- package/templates/dashboard/assets/mermaid-renderer.js +58 -8
- package/templates/development/README.md +52 -0
- package/templates/development/codebase-map.md +11 -0
- package/templates/development/cross-repo-debugging.md +18 -0
- package/templates/development/external-context/service-template.md +33 -0
- package/templates/development/external-source-packs/README.md +24 -0
- package/templates/development/external-source-packs/digest-template.md +28 -0
- package/templates/development/local-setup.md +16 -0
- package/templates/development/stubs-and-mocks.md +11 -0
- package/templates/integrations/README.md +40 -0
- package/templates/integrations/api-contract.md +42 -0
- package/templates/integrations/event-contract.md +46 -0
- package/templates/integrations/third-party/vendor-template.md +42 -0
- package/templates/integrations/webhook-contract.md +41 -0
- package/templates/planning/brief.md +32 -0
- package/templates/planning/lesson_candidates.md +58 -0
- package/templates/planning/long-running-task-contract.md +7 -0
- package/templates/planning/module_brief.md +25 -0
- package/templates/planning/module_session_prompt.md +6 -0
- package/templates/planning/task_plan.md +7 -5
- package/templates/planning/{visual_roadmap.md → visual_map.md} +24 -2
- package/templates/reference/docs-library-standard.md +31 -0
- package/templates/reference/execution-workflow-standard.md +4 -2
- package/templates/reference/external-source-intake-standard.md +82 -0
- package/templates/reference/harness-ledger-standard.md +1 -0
- package/templates/reference/repo-governance-standard.md +6 -4
- package/templates/reference/walkthrough-standard.md +2 -1
- package/templates/walkthrough/walkthrough-template.md +2 -2
- package/templates-zh-CN/AGENTS.md.template +69 -70
- package/templates-zh-CN/architecture/Architecture-SSoT.md +21 -0
- package/templates-zh-CN/architecture/README.md +51 -0
- package/templates-zh-CN/architecture/critical-flows.md +24 -0
- package/templates-zh-CN/architecture/local-repo-context.md +20 -0
- package/templates-zh-CN/architecture/service-catalog.md +17 -0
- package/templates-zh-CN/architecture/services/service-template.md +31 -0
- package/templates-zh-CN/architecture/system-map.md +22 -0
- package/templates-zh-CN/development/README.md +54 -0
- package/templates-zh-CN/development/codebase-map.md +11 -0
- package/templates-zh-CN/development/cross-repo-debugging.md +18 -0
- package/templates-zh-CN/development/external-context/service-template.md +33 -0
- package/templates-zh-CN/development/external-source-packs/README.md +24 -0
- package/templates-zh-CN/development/external-source-packs/digest-template.md +28 -0
- package/templates-zh-CN/development/local-setup.md +16 -0
- package/templates-zh-CN/development/stubs-and-mocks.md +11 -0
- package/templates-zh-CN/integrations/README.md +42 -0
- package/templates-zh-CN/integrations/api-contract.md +42 -0
- package/templates-zh-CN/integrations/event-contract.md +46 -0
- package/templates-zh-CN/integrations/third-party/vendor-template.md +42 -0
- package/templates-zh-CN/integrations/webhook-contract.md +41 -0
- package/templates-zh-CN/planning/brief.md +32 -0
- package/templates-zh-CN/planning/lesson_candidates.md +58 -0
- package/templates-zh-CN/planning/long-running-task-contract.md +1 -1
- package/templates-zh-CN/planning/module_brief.md +25 -0
- package/templates-zh-CN/planning/module_plan.md +2 -2
- package/templates-zh-CN/planning/module_session_prompt.md +4 -3
- package/templates-zh-CN/planning/task_plan.md +10 -4
- package/templates-zh-CN/planning/{visual_roadmap.md → visual_map.md} +21 -2
- package/templates-zh-CN/reference/docs-library-standard.md +35 -0
- package/templates-zh-CN/reference/execution-workflow-standard.md +9 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +82 -0
- package/templates-zh-CN/reference/harness-ledger-standard.md +5 -2
- package/templates-zh-CN/reference/repo-governance-standard.md +2 -0
- package/templates-zh-CN/reference/walkthrough-standard.md +4 -4
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +2 -2
- package/templates-zh-CN/dashboard/assets/app.css +0 -399
- package/templates-zh-CN/dashboard/assets/app.js +0 -435
- package/templates-zh-CN/dashboard/assets/i18n.js +0 -47
- package/templates-zh-CN/dashboard/assets/markdown-reader.js +0 -116
- package/templates-zh-CN/dashboard/assets/mermaid-renderer.js +0 -59
- package/templates-zh-CN/dashboard/index.html +0 -18
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import {
|
|
6
|
+
repoRoot,
|
|
7
|
+
visualMapFile,
|
|
8
|
+
normalizeTarget,
|
|
9
|
+
toPosix,
|
|
10
|
+
exists,
|
|
11
|
+
existsInDocs,
|
|
12
|
+
readFileSafe,
|
|
13
|
+
readBundledTemplate,
|
|
14
|
+
walkFiles,
|
|
15
|
+
normalizeLocale,
|
|
16
|
+
localizedTemplateSource,
|
|
17
|
+
} from "./core-shared.mjs";
|
|
18
|
+
|
|
19
|
+
export const capabilityDefinitions = {
|
|
20
|
+
core: {
|
|
21
|
+
description: "Planning loop and task execution records.",
|
|
22
|
+
selectWhen: "Always install. This is the required document kernel.",
|
|
23
|
+
default: true,
|
|
24
|
+
dependencies: [],
|
|
25
|
+
artifacts: ["docs/09-PLANNING"],
|
|
26
|
+
},
|
|
27
|
+
"module-parallel": {
|
|
28
|
+
description: "Module registry, module plans, session prompts, and worker handoff.",
|
|
29
|
+
selectWhen: "Use only when the project has two or more independent modules that need parallel ownership.",
|
|
30
|
+
default: false,
|
|
31
|
+
dependencies: ["core"],
|
|
32
|
+
artifacts: ["docs/09-PLANNING/Module-Registry.md", "docs/09-PLANNING/MODULES"],
|
|
33
|
+
},
|
|
34
|
+
"subagent-worker": {
|
|
35
|
+
description: "Commit-backed worker handoff protocol for code-changing subagents.",
|
|
36
|
+
selectWhen: "Use only when code-changing subagents will work in dedicated worktrees with commit-backed handoff.",
|
|
37
|
+
default: false,
|
|
38
|
+
dependencies: ["module-parallel"],
|
|
39
|
+
artifacts: ["docs/09-PLANNING/MODULES"],
|
|
40
|
+
},
|
|
41
|
+
"adversarial-review": {
|
|
42
|
+
description: "Machine-gateable adversarial review reports and verifier output contract.",
|
|
43
|
+
selectWhen: "Use when release, architecture, security, data, or strategy risk requires an independent review artifact.",
|
|
44
|
+
default: false,
|
|
45
|
+
dependencies: ["core"],
|
|
46
|
+
artifacts: ["docs/09-PLANNING/TASKS"],
|
|
47
|
+
},
|
|
48
|
+
"long-running-task": {
|
|
49
|
+
description: "Long-running task contract with review cadence and stop conditions.",
|
|
50
|
+
selectWhen: "Use when agents may run across many loops without user confirmation after every step.",
|
|
51
|
+
default: false,
|
|
52
|
+
dependencies: ["core"],
|
|
53
|
+
artifacts: ["docs/09-PLANNING/TASKS/_task-template/long-running-task-contract.md"],
|
|
54
|
+
},
|
|
55
|
+
"dashboard": {
|
|
56
|
+
description: "Read-only HTML dashboard generated from harness status JSON.",
|
|
57
|
+
selectWhen: "Use when users or agents need a local read-only status surface.",
|
|
58
|
+
default: false,
|
|
59
|
+
dependencies: ["core"],
|
|
60
|
+
artifacts: [],
|
|
61
|
+
},
|
|
62
|
+
"safe-adoption": {
|
|
63
|
+
description: "Legacy compatibility and assisted capability adoption.",
|
|
64
|
+
selectWhen: "Use when adopting v1.0 into an existing harness project without rewriting history.",
|
|
65
|
+
default: false,
|
|
66
|
+
dependencies: ["core"],
|
|
67
|
+
artifacts: [],
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const capabilityAliases = {
|
|
72
|
+
"review-contract": "adversarial-review",
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
export const allowedCapabilityStates = new Set(["scaffolded", "configured", "verified"]);
|
|
76
|
+
export const userInstallTargets = {
|
|
77
|
+
codex: [".codex", "skills", "coding-agent-harness"],
|
|
78
|
+
claude: [".claude", "skills", "coding-agent-harness"],
|
|
79
|
+
gemini: [".gemini", "skills", "coding-agent-harness"],
|
|
80
|
+
openclaw: [".openclaw", "skills", "coding-agent-harness"],
|
|
81
|
+
agents: [".agents", "skills", "coding-agent-harness"],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export function readCapabilityRegistry(target) {
|
|
85
|
+
const registryPath = path.join(target.projectRoot, ".harness-capabilities.json");
|
|
86
|
+
if (!fs.existsSync(registryPath)) {
|
|
87
|
+
return {
|
|
88
|
+
mode: "legacy-compat",
|
|
89
|
+
path: registryPath,
|
|
90
|
+
capabilities: [{ name: "core", state: "configured" }],
|
|
91
|
+
locale: "en-US",
|
|
92
|
+
raw: null,
|
|
93
|
+
errors: [],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const raw = JSON.parse(fs.readFileSync(registryPath, "utf8"));
|
|
99
|
+
const locale = normalizeLocale(raw.locale);
|
|
100
|
+
const capabilities = Array.isArray(raw.capabilities)
|
|
101
|
+
? raw.capabilities.map((entry) =>
|
|
102
|
+
typeof entry === "string"
|
|
103
|
+
? { name: normalizeCapabilityName(entry), state: "scaffolded" }
|
|
104
|
+
: { name: normalizeCapabilityName(entry.name), state: entry.state || "scaffolded" },
|
|
105
|
+
)
|
|
106
|
+
: [];
|
|
107
|
+
return { mode: "declared-capability", path: registryPath, capabilities, raw, locale, errors: [] };
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return { mode: "declared-capability", path: registryPath, capabilities: [], raw: null, errors: [error.message] };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function normalizeCapabilityName(name) {
|
|
114
|
+
return capabilityAliases[name] || name;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function validateSourcePackageBoundary(targetInput = ".") {
|
|
118
|
+
const root = path.resolve(targetInput || ".");
|
|
119
|
+
const gitProbe = spawnSync("git", ["-C", root, "rev-parse", "--is-inside-work-tree"], { encoding: "utf8" });
|
|
120
|
+
if (gitProbe.status !== 0) return { failures: [], warnings: [] };
|
|
121
|
+
const staged = spawnSync("git", ["-C", root, "diff", "--cached", "--name-only", "-z"], { encoding: "utf8" });
|
|
122
|
+
if (staged.status !== 0) return { failures: [], warnings: [`could not inspect staged files: ${staged.stderr.trim() || staged.status}`] };
|
|
123
|
+
const localOnly = staged.stdout
|
|
124
|
+
.split("\0")
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.filter((file) => file === "AGENTS.md" || file === "CLAUDE.md" || file === "docs" || file.startsWith("docs/") || file === ".harness-private" || file.startsWith(".harness-private/"));
|
|
127
|
+
return {
|
|
128
|
+
failures: localOnly.map((file) => `private local-only file staged: ${file}`),
|
|
129
|
+
warnings: [],
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function detectCapabilities(target) {
|
|
134
|
+
const detected = new Set(["core"]);
|
|
135
|
+
if (existsInDocs(target, "09-PLANNING/Module-Registry.md")) detected.add("module-parallel");
|
|
136
|
+
if (existsInDocs(target, "11-REFERENCE/adversarial-review-standard.md")) detected.add("adversarial-review");
|
|
137
|
+
if (
|
|
138
|
+
existsInDocs(target, "11-REFERENCE/long-running-task-standard.md") ||
|
|
139
|
+
existsInDocs(target, "09-PLANNING/TASKS/_task-template/long-running-task-contract.md")
|
|
140
|
+
) {
|
|
141
|
+
detected.add("long-running-task");
|
|
142
|
+
}
|
|
143
|
+
return [...detected];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function buildInstallReport({ target, locale, capabilities, changes, dryRun = false, operation = "init" }) {
|
|
147
|
+
const selected = new Set(capabilities.map(normalizeCapabilityName));
|
|
148
|
+
return {
|
|
149
|
+
operation,
|
|
150
|
+
dryRun,
|
|
151
|
+
target: target.projectRoot,
|
|
152
|
+
locale,
|
|
153
|
+
capabilities: Object.entries(capabilityDefinitions).map(([name, definition]) => ({
|
|
154
|
+
name,
|
|
155
|
+
selected: selected.has(name),
|
|
156
|
+
default: definition.default === true,
|
|
157
|
+
dependencies: definition.dependencies,
|
|
158
|
+
description: definition.description,
|
|
159
|
+
selectWhen: definition.selectWhen,
|
|
160
|
+
})),
|
|
161
|
+
selectedCapabilities: capabilities,
|
|
162
|
+
created: changes.filter((change) => ["create", "would-create"].includes(change.action)).map((change) => change.destination),
|
|
163
|
+
skipped: changes.filter((change) => change.action === "skip-existing").map((change) => change.destination),
|
|
164
|
+
agentInstructions: [
|
|
165
|
+
"Agents must choose locale during Decide and pass --locale zh-CN|en-US explicitly in non-interactive installs.",
|
|
166
|
+
"Use core for every install; add optional capabilities only when their selectWhen rule is true.",
|
|
167
|
+
"After scaffold, run Configure before marking capabilities configured or verified.",
|
|
168
|
+
"Run harness check/status/dashboard and record residuals before delivery.",
|
|
169
|
+
],
|
|
170
|
+
verificationCommands: [
|
|
171
|
+
`harness check --profile target-project ${target.projectRoot}`,
|
|
172
|
+
`harness status --json ${target.projectRoot}`,
|
|
173
|
+
`harness dashboard --out /tmp/harness-dashboard.html ${target.projectRoot}`,
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function packageVersion() {
|
|
179
|
+
try {
|
|
180
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8"));
|
|
181
|
+
return pkg.version || "";
|
|
182
|
+
} catch {
|
|
183
|
+
return "";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function userHome(home = "") {
|
|
188
|
+
return path.resolve(home || os.homedir());
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function normalizeUserAgent(agent = "codex") {
|
|
192
|
+
const normalized = String(agent || "codex").toLowerCase();
|
|
193
|
+
if (normalized === "all") return Object.keys(userInstallTargets);
|
|
194
|
+
if (!userInstallTargets[normalized]) throw new Error(`Unknown user agent target: ${agent}`);
|
|
195
|
+
return [normalized];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function targetForUserAgent(agent, home = "") {
|
|
199
|
+
return path.join(userHome(home), ...userInstallTargets[agent]);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function skillPackageEntries() {
|
|
203
|
+
return [
|
|
204
|
+
"README.md",
|
|
205
|
+
"CHANGELOG.md",
|
|
206
|
+
"SKILL.md",
|
|
207
|
+
"LICENSE",
|
|
208
|
+
"package.json",
|
|
209
|
+
"references",
|
|
210
|
+
"templates",
|
|
211
|
+
"templates-zh-CN",
|
|
212
|
+
"scripts",
|
|
213
|
+
"docs-release",
|
|
214
|
+
"examples",
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function listPackageFiles() {
|
|
219
|
+
const files = [];
|
|
220
|
+
function walk(relativePath) {
|
|
221
|
+
const full = path.join(repoRoot, relativePath);
|
|
222
|
+
if (!fs.existsSync(full)) return;
|
|
223
|
+
const stat = fs.statSync(full);
|
|
224
|
+
if (stat.isDirectory()) {
|
|
225
|
+
for (const entry of fs.readdirSync(full)) walk(path.join(relativePath, entry));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (stat.isFile()) files.push(toPosix(relativePath));
|
|
229
|
+
}
|
|
230
|
+
for (const entry of skillPackageEntries()) walk(entry);
|
|
231
|
+
return files.sort();
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function copySkillPackage(targetRoot, { dryRun = false, force = false } = {}) {
|
|
235
|
+
const changes = [];
|
|
236
|
+
for (const relativeFile of listPackageFiles()) {
|
|
237
|
+
const source = path.join(repoRoot, relativeFile);
|
|
238
|
+
const destination = path.join(targetRoot, relativeFile);
|
|
239
|
+
const existsAlready = fs.existsSync(destination);
|
|
240
|
+
const action = existsAlready ? (force ? "overwrite" : "skip-existing") : dryRun ? "would-create" : "create";
|
|
241
|
+
changes.push({ source: relativeFile, destination, action });
|
|
242
|
+
if (dryRun || (existsAlready && !force)) continue;
|
|
243
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true });
|
|
244
|
+
fs.copyFileSync(source, destination);
|
|
245
|
+
}
|
|
246
|
+
return changes;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function installUserSkill({ agent = "codex", home = "", dryRun = false, force = false } = {}) {
|
|
250
|
+
const agents = normalizeUserAgent(agent);
|
|
251
|
+
const targets = agents.map((targetAgent) => {
|
|
252
|
+
const target = targetForUserAgent(targetAgent, home);
|
|
253
|
+
const changes = copySkillPackage(target, { dryRun, force });
|
|
254
|
+
return {
|
|
255
|
+
agent: targetAgent,
|
|
256
|
+
target,
|
|
257
|
+
changes,
|
|
258
|
+
created: changes.filter((change) => ["create", "would-create"].includes(change.action)).length,
|
|
259
|
+
overwritten: changes.filter((change) => change.action === "overwrite").length,
|
|
260
|
+
skipped: changes.filter((change) => change.action === "skip-existing").length,
|
|
261
|
+
};
|
|
262
|
+
});
|
|
263
|
+
const changed = targets.some((target) => target.created > 0 || target.overwritten > 0);
|
|
264
|
+
const onlySkipped = targets.every((target) => target.created === 0 && target.overwritten === 0 && target.skipped > 0);
|
|
265
|
+
return {
|
|
266
|
+
operation: "install-user",
|
|
267
|
+
status: dryRun ? "dry-run" : changed ? "installed" : onlySkipped ? "already-present" : "no-op",
|
|
268
|
+
dryRun,
|
|
269
|
+
force,
|
|
270
|
+
version: packageVersion(),
|
|
271
|
+
source: repoRoot,
|
|
272
|
+
targets,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function readInstalledVersion(targetRoot) {
|
|
277
|
+
try {
|
|
278
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(targetRoot, "package.json"), "utf8"));
|
|
279
|
+
return pkg.version || "";
|
|
280
|
+
} catch {
|
|
281
|
+
return "";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function commandOnPath(command) {
|
|
286
|
+
const paths = (process.env.PATH || "").split(path.delimiter).filter(Boolean);
|
|
287
|
+
const extensions = process.platform === "win32" ? (process.env.PATHEXT || ".EXE;.CMD;.BAT").split(";") : [""];
|
|
288
|
+
for (const base of paths) {
|
|
289
|
+
for (const extension of extensions) {
|
|
290
|
+
const candidate = path.join(base, `${command}${extension}`);
|
|
291
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return "";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export function doctorUserSkill({ agent = "codex", home = "" } = {}) {
|
|
298
|
+
const required = [
|
|
299
|
+
"SKILL.md",
|
|
300
|
+
"package.json",
|
|
301
|
+
"references",
|
|
302
|
+
"templates",
|
|
303
|
+
"templates-zh-CN",
|
|
304
|
+
"scripts/harness.mjs",
|
|
305
|
+
"docs-release/guides/agent-installation.md",
|
|
306
|
+
];
|
|
307
|
+
const targets = normalizeUserAgent(agent).map((targetAgent) => {
|
|
308
|
+
const target = targetForUserAgent(targetAgent, home);
|
|
309
|
+
const missing = required.filter((relativePath) => !fs.existsSync(path.join(target, relativePath)));
|
|
310
|
+
return {
|
|
311
|
+
agent: targetAgent,
|
|
312
|
+
target,
|
|
313
|
+
status: missing.length === 0 ? "pass" : "fail",
|
|
314
|
+
version: readInstalledVersion(target),
|
|
315
|
+
missing,
|
|
316
|
+
};
|
|
317
|
+
});
|
|
318
|
+
const harnessCommand = commandOnPath("harness");
|
|
319
|
+
return {
|
|
320
|
+
operation: "doctor-user",
|
|
321
|
+
status: targets.every((target) => target.status === "pass") ? "pass" : "fail",
|
|
322
|
+
version: packageVersion(),
|
|
323
|
+
harnessCommand: harnessCommand || null,
|
|
324
|
+
targets,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export function validateCapabilities(target) {
|
|
329
|
+
const registry = readCapabilityRegistry(target);
|
|
330
|
+
const detected = detectCapabilities(target);
|
|
331
|
+
const failures = [];
|
|
332
|
+
const warnings = [];
|
|
333
|
+
const byName = new Map(registry.capabilities.map((capability) => [capability.name, capability]));
|
|
334
|
+
|
|
335
|
+
for (const error of registry.errors) failures.push(`invalid .harness-capabilities.json: ${error}`);
|
|
336
|
+
for (const capability of registry.capabilities) {
|
|
337
|
+
if (!capabilityDefinitions[capability.name]) {
|
|
338
|
+
failures.push(`unknown capability: ${capability.name}`);
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
if (!allowedCapabilityStates.has(capability.state)) {
|
|
342
|
+
failures.push(`capability ${capability.name} has invalid state: ${capability.state}`);
|
|
343
|
+
}
|
|
344
|
+
for (const dependency of capabilityDefinitions[capability.name].dependencies) {
|
|
345
|
+
if (!byName.has(dependency)) failures.push(`capability ${capability.name} missing dependency: ${dependency}`);
|
|
346
|
+
}
|
|
347
|
+
if (registry.mode === "declared-capability") {
|
|
348
|
+
for (const artifact of capabilityDefinitions[capability.name].artifacts) {
|
|
349
|
+
if (!exists(target, artifact)) {
|
|
350
|
+
failures.push(`capability ${capability.name} missing required artifact: ${artifact}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (registry.mode === "declared-capability") {
|
|
357
|
+
for (const capability of detected) {
|
|
358
|
+
if (!byName.has(capability)) warnings.push(`orphan capability artifact detected without declaration: ${capability}`);
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
warnings.push("legacy-compat mode: no .harness-capabilities.json; adoption suggestion is available");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return { registry, detected, failures, warnings };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
export function plannedInitFiles(capabilities = ["core"], { locale = "en-US" } = {}) {
|
|
369
|
+
const files = [
|
|
370
|
+
["AGENTS.md", "templates/AGENTS.md.template"],
|
|
371
|
+
["CLAUDE.md", "templates/CLAUDE.md.template"],
|
|
372
|
+
["docs/Harness-Ledger.md", "templates/ledger/Harness-Ledger.md"],
|
|
373
|
+
["docs/03-ARCHITECTURE/README.md", "templates/architecture/README.md"],
|
|
374
|
+
["docs/03-ARCHITECTURE/Architecture-SSoT.md", "templates/architecture/Architecture-SSoT.md"],
|
|
375
|
+
["docs/03-ARCHITECTURE/local-repo-context.md", "templates/architecture/local-repo-context.md"],
|
|
376
|
+
["docs/03-ARCHITECTURE/system-map.md", "templates/architecture/system-map.md"],
|
|
377
|
+
["docs/03-ARCHITECTURE/service-catalog.md", "templates/architecture/service-catalog.md"],
|
|
378
|
+
["docs/03-ARCHITECTURE/critical-flows.md", "templates/architecture/critical-flows.md"],
|
|
379
|
+
["docs/03-ARCHITECTURE/services/_service-template.md", "templates/architecture/services/service-template.md"],
|
|
380
|
+
["docs/04-DEVELOPMENT/README.md", "templates/development/README.md"],
|
|
381
|
+
["docs/04-DEVELOPMENT/local-setup.md", "templates/development/local-setup.md"],
|
|
382
|
+
["docs/04-DEVELOPMENT/codebase-map.md", "templates/development/codebase-map.md"],
|
|
383
|
+
["docs/04-DEVELOPMENT/external-context/_service-template.md", "templates/development/external-context/service-template.md"],
|
|
384
|
+
["docs/04-DEVELOPMENT/external-source-packs/README.md", "templates/development/external-source-packs/README.md"],
|
|
385
|
+
["docs/04-DEVELOPMENT/external-source-packs/_digest-template.md", "templates/development/external-source-packs/digest-template.md"],
|
|
386
|
+
["docs/04-DEVELOPMENT/stubs-and-mocks.md", "templates/development/stubs-and-mocks.md"],
|
|
387
|
+
["docs/04-DEVELOPMENT/cross-repo-debugging.md", "templates/development/cross-repo-debugging.md"],
|
|
388
|
+
["docs/06-INTEGRATIONS/README.md", "templates/integrations/README.md"],
|
|
389
|
+
["docs/06-INTEGRATIONS/_api-contract-template.md", "templates/integrations/api-contract.md"],
|
|
390
|
+
["docs/06-INTEGRATIONS/_event-contract-template.md", "templates/integrations/event-contract.md"],
|
|
391
|
+
["docs/06-INTEGRATIONS/_webhook-contract-template.md", "templates/integrations/webhook-contract.md"],
|
|
392
|
+
["docs/06-INTEGRATIONS/third-party/_vendor-template.md", "templates/integrations/third-party/vendor-template.md"],
|
|
393
|
+
["docs/09-PLANNING/TASKS/_task-template/brief.md", "templates/planning/brief.md"],
|
|
394
|
+
["docs/09-PLANNING/TASKS/_task-template/task_plan.md", "templates/planning/task_plan.md"],
|
|
395
|
+
["docs/09-PLANNING/TASKS/_task-template/execution_strategy.md", "templates/planning/execution_strategy.md"],
|
|
396
|
+
[`docs/09-PLANNING/TASKS/_task-template/${visualMapFile}`, "templates/planning/visual_map.md"],
|
|
397
|
+
["docs/09-PLANNING/TASKS/_task-template/findings.md", "templates/planning/findings.md"],
|
|
398
|
+
["docs/09-PLANNING/TASKS/_task-template/progress.md", "templates/planning/progress.md"],
|
|
399
|
+
["docs/09-PLANNING/TASKS/_task-template/review.md", "templates/planning/review.md"],
|
|
400
|
+
["docs/05-TEST-QA/Regression-SSoT.md", "templates/ssot/Regression-SSoT.md"],
|
|
401
|
+
["docs/05-TEST-QA/Cadence-Ledger.md", "templates/regression/Cadence-Ledger.md"],
|
|
402
|
+
["docs/01-GOVERNANCE/Lessons-SSoT.md", "templates/ssot/Lessons-SSoT.md"],
|
|
403
|
+
["docs/10-WALKTHROUGH/_walkthrough-template.md", "templates/walkthrough/walkthrough-template.md"],
|
|
404
|
+
["docs/10-WALKTHROUGH/Closeout-SSoT.md", "templates/walkthrough/Closeout-SSoT.md"],
|
|
405
|
+
["docs/11-REFERENCE/external-source-intake-standard.md", "templates/reference/external-source-intake-standard.md"],
|
|
406
|
+
];
|
|
407
|
+
if (capabilities.includes("module-parallel")) {
|
|
408
|
+
files.push(["docs/09-PLANNING/Module-Registry.md", "templates/ssot/Module-Registry.md"]);
|
|
409
|
+
files.push(["docs/09-PLANNING/MODULES/Session-Prompt-Pack.md", "templates/planning/module_session_prompt.md"]);
|
|
410
|
+
files.push(["docs/09-PLANNING/MODULES/_module-template/brief.md", "templates/planning/module_brief.md"]);
|
|
411
|
+
files.push(["docs/09-PLANNING/MODULES/_module-template/module_plan.md", "templates/planning/module_plan.md"]);
|
|
412
|
+
files.push(["docs/09-PLANNING/MODULES/_module-template/execution_strategy.md", "templates/planning/execution_strategy.md"]);
|
|
413
|
+
files.push([`docs/09-PLANNING/MODULES/_module-template/${visualMapFile}`, "templates/planning/visual_map.md"]);
|
|
414
|
+
files.push(["docs/09-PLANNING/MODULES/_module-template/session_prompt.md", "templates/planning/module_session_prompt.md"]);
|
|
415
|
+
files.push(["docs/09-PLANNING/MODULES/_task-template/task_plan.md", "templates/planning/task_plan.md"]);
|
|
416
|
+
files.push(["docs/09-PLANNING/MODULES/_task-template/execution_strategy.md", "templates/planning/execution_strategy.md"]);
|
|
417
|
+
files.push([`docs/09-PLANNING/MODULES/_task-template/${visualMapFile}`, "templates/planning/visual_map.md"]);
|
|
418
|
+
files.push(["docs/09-PLANNING/MODULES/_task-template/findings.md", "templates/planning/findings.md"]);
|
|
419
|
+
files.push(["docs/09-PLANNING/MODULES/_task-template/progress.md", "templates/planning/progress.md"]);
|
|
420
|
+
files.push(["docs/09-PLANNING/MODULES/_task-template/review.md", "templates/planning/review.md"]);
|
|
421
|
+
}
|
|
422
|
+
if (capabilities.includes("long-running-task")) {
|
|
423
|
+
files.push(["docs/09-PLANNING/TASKS/_task-template/long-running-task-contract.md", "templates/planning/long-running-task-contract.md"]);
|
|
424
|
+
}
|
|
425
|
+
return files.map(([destination, source]) => [destination, localizedTemplateSource(source, locale)]);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function writeInitFiles(targetInput, capabilities, { dryRun = true, locale = "en-US", addNpmScripts = false } = {}) {
|
|
429
|
+
const target = normalizeTarget(targetInput);
|
|
430
|
+
const normalizedCapabilities = [...new Set(capabilities.map(normalizeCapabilityName))];
|
|
431
|
+
const normalizedLocale = normalizeLocale(locale);
|
|
432
|
+
const existingRegistry = readCapabilityRegistry(target);
|
|
433
|
+
if (existingRegistry.raw) {
|
|
434
|
+
const installed = new Set(existingRegistry.capabilities.map((capability) => capability.name));
|
|
435
|
+
const requested = new Set(normalizedCapabilities);
|
|
436
|
+
const same =
|
|
437
|
+
installed.size === requested.size &&
|
|
438
|
+
[...installed].every((capability) => requested.has(capability));
|
|
439
|
+
if (!same) {
|
|
440
|
+
throw new Error("Existing capability registry differs from requested init capabilities; use add-capability instead.");
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const planned = plannedInitFiles(normalizedCapabilities, { locale: normalizedLocale });
|
|
444
|
+
const changes = [];
|
|
445
|
+
for (const [destination, source] of planned) {
|
|
446
|
+
const destinationPath = path.join(target.projectRoot, destination);
|
|
447
|
+
const sourcePath = path.join(repoRoot, source);
|
|
448
|
+
const existsAlready = fs.existsSync(destinationPath);
|
|
449
|
+
changes.push({ destination, source, action: existsAlready ? "skip-existing" : dryRun ? "would-create" : "create" });
|
|
450
|
+
if (!dryRun && !existsAlready) {
|
|
451
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
452
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (addNpmScripts) {
|
|
456
|
+
changes.push(...writeNpmScripts(target, { dryRun }));
|
|
457
|
+
}
|
|
458
|
+
const registry = {
|
|
459
|
+
version: 1,
|
|
460
|
+
locale: normalizedLocale,
|
|
461
|
+
capabilities: normalizedCapabilities.map((name) => ({ name, state: "scaffolded" })),
|
|
462
|
+
};
|
|
463
|
+
if (!dryRun) {
|
|
464
|
+
const registryPath = path.join(target.projectRoot, ".harness-capabilities.json");
|
|
465
|
+
if (!fs.existsSync(registryPath)) fs.writeFileSync(registryPath, `${JSON.stringify(registry, null, 2)}\n`);
|
|
466
|
+
}
|
|
467
|
+
const report = buildInstallReport({ target, locale: normalizedLocale, capabilities: normalizedCapabilities, changes, dryRun, operation: "init" });
|
|
468
|
+
return { target, capabilities: normalizedCapabilities, locale: normalizedLocale, changes, nextCommands: initNextCommands(), report };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function initNextCommands() {
|
|
472
|
+
return [
|
|
473
|
+
"npx --yes coding-agent-harness dev .",
|
|
474
|
+
"npx --yes coding-agent-harness dashboard --out-dir tmp/harness-dashboard .",
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function writeNpmScripts(target, { dryRun = true } = {}) {
|
|
479
|
+
const packagePath = path.join(target.projectRoot, "package.json");
|
|
480
|
+
if (!fs.existsSync(packagePath)) throw new Error("init --add-npm-scripts requires an existing package.json");
|
|
481
|
+
const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
482
|
+
const scripts = { ...(pkg.scripts || {}) };
|
|
483
|
+
const additions = {
|
|
484
|
+
"harness:dev": "coding-agent-harness dev .",
|
|
485
|
+
"harness:dashboard": "coding-agent-harness dashboard --out-dir tmp/harness-dashboard .",
|
|
486
|
+
};
|
|
487
|
+
let changed = false;
|
|
488
|
+
const scriptChanges = [];
|
|
489
|
+
for (const [name, command] of Object.entries(additions)) {
|
|
490
|
+
if (scripts[name]) {
|
|
491
|
+
scriptChanges.push({ destination: "package.json", source: `scripts.${name}`, action: "skip-existing-script" });
|
|
492
|
+
continue;
|
|
493
|
+
}
|
|
494
|
+
scripts[name] = command;
|
|
495
|
+
changed = true;
|
|
496
|
+
}
|
|
497
|
+
if (!changed) return scriptChanges;
|
|
498
|
+
if (!dryRun) {
|
|
499
|
+
fs.writeFileSync(packagePath, `${JSON.stringify({ ...pkg, scripts }, null, 2)}\n`);
|
|
500
|
+
}
|
|
501
|
+
return [{ destination: "package.json", source: "npm-scripts", action: dryRun ? "would-update-scripts" : "update-scripts" }, ...scriptChanges];
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function addCapability(targetInput, capabilityName, { dryRun = true, locale = "" } = {}) {
|
|
505
|
+
const target = normalizeTarget(targetInput);
|
|
506
|
+
const normalizedCapability = normalizeCapabilityName(capabilityName);
|
|
507
|
+
if (!capabilityDefinitions[normalizedCapability]) throw new Error(`Unknown capability: ${capabilityName}`);
|
|
508
|
+
const registry = readCapabilityRegistry(target);
|
|
509
|
+
const normalizedLocale = normalizeLocale(registry.raw ? registry.locale : locale || "en-US");
|
|
510
|
+
const capabilityMap = new Map(registry.capabilities.map((capability) => [capability.name, capability]));
|
|
511
|
+
for (const dependency of capabilityDefinitions[normalizedCapability].dependencies) {
|
|
512
|
+
if (!capabilityMap.has(dependency)) capabilityMap.set(dependency, { name: dependency, state: "scaffolded" });
|
|
513
|
+
}
|
|
514
|
+
if (!capabilityMap.has(normalizedCapability)) capabilityMap.set(normalizedCapability, { name: normalizedCapability, state: "scaffolded" });
|
|
515
|
+
const next = { version: 1, locale: normalizedLocale, capabilities: [...capabilityMap.values()] };
|
|
516
|
+
const scaffold = plannedInitFiles([...capabilityMap.keys()], { locale: normalizedLocale });
|
|
517
|
+
const changes = [];
|
|
518
|
+
for (const [destination, source] of scaffold) {
|
|
519
|
+
const destinationPath = path.join(target.projectRoot, destination);
|
|
520
|
+
const sourcePath = path.join(repoRoot, source);
|
|
521
|
+
const existsAlready = fs.existsSync(destinationPath);
|
|
522
|
+
changes.push({ destination, source, action: existsAlready ? "skip-existing" : dryRun ? "would-create" : "create" });
|
|
523
|
+
if (!dryRun && !existsAlready) {
|
|
524
|
+
fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
|
|
525
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (!dryRun) {
|
|
529
|
+
fs.writeFileSync(path.join(target.projectRoot, ".harness-capabilities.json"), `${JSON.stringify(next, null, 2)}\n`);
|
|
530
|
+
}
|
|
531
|
+
const report = buildInstallReport({ target, locale: normalizedLocale, capabilities: [...capabilityMap.keys()], changes, dryRun, operation: "add-capability" });
|
|
532
|
+
return { target, dryRun, registry: next, changes, report };
|
|
533
|
+
}
|