coding-agent-harness 1.0.1 → 1.0.4
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 +44 -0
- package/CONTRIBUTING.md +98 -0
- package/README.en-US.md +14 -0
- package/README.md +230 -80
- package/README.zh-CN.md +290 -0
- package/SKILL.md +132 -198
- package/docs-release/README.md +80 -9
- package/docs-release/architecture/overview.md +298 -28
- package/docs-release/architecture/overview.zh-CN.md +292 -0
- package/docs-release/assets/dashboard-overview.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 +237 -0
- package/docs-release/guides/agent-installation.md +149 -27
- package/docs-release/guides/contributing.md +100 -0
- package/docs-release/guides/contributing.zh-CN.md +99 -0
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +113 -0
- package/docs-release/guides/document-audience-and-surfaces.md +113 -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 +373 -0
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +350 -0
- package/docs-release/guides/migration-playbook.en-US.md +324 -0
- package/docs-release/guides/migration-playbook.md +328 -0
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +254 -0
- package/docs-release/guides/parent-control-repository-pattern.md +254 -0
- package/docs-release/guides/preset-development.md +214 -0
- package/docs-release/guides/repository-operating-models.en-US.md +197 -0
- package/docs-release/guides/repository-operating-models.md +197 -0
- package/docs-release/guides/task-state-machine.en-US.md +207 -0
- package/docs-release/guides/task-state-machine.md +214 -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/findings.md +7 -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 +10 -3
- package/presets/legacy-migration/checks/preset-check.mjs +3 -0
- package/presets/legacy-migration/preset.yaml +134 -0
- package/presets/legacy-migration/scripts/plan-work-queue.mjs +4 -0
- package/presets/legacy-migration/scripts/scaffold-task-contracts.mjs +4 -0
- package/presets/legacy-migration/templates/execution_strategy.append.md +18 -0
- package/presets/legacy-migration/templates/findings.seed.md +17 -0
- package/presets/legacy-migration/templates/review.seed.md +12 -0
- package/presets/legacy-migration/templates/task_plan.append.md +9 -0
- package/presets/legacy-migration/templates/visual_map.append.md +12 -0
- package/presets/legacy-migration/workbench/dashboard-panels.yaml +2 -0
- package/presets/legacy-migration/workbench/migration-queue.schema.json +23 -0
- package/presets/lesson-sedimentation/preset.yaml +23 -0
- package/presets/lesson-sedimentation/templates/prompt.md +23 -0
- package/presets/module/preset.yaml +25 -0
- package/presets/module/templates/execution_strategy.append.md +8 -0
- package/presets/module/templates/task_plan.append.md +17 -0
- package/presets/standard-task/preset.yaml +31 -0
- package/presets/standard-task/templates/task_plan.append.md +7 -0
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +5 -5
- package/references/delivery-operating-model-standard.md +3 -3
- package/references/docs-directory-standard.md +53 -10
- package/references/external-source-intake-standard.md +75 -0
- package/references/harness-ledger.md +53 -94
- package/references/legacy-12-phase-bootstrap.md +41 -0
- package/references/lessons-governance.md +100 -88
- package/references/module-parallel-standard.md +14 -14
- package/references/planning-loop.md +51 -7
- package/references/project-onboarding-audit.md +10 -0
- package/references/pull-request-standard.md +118 -0
- package/references/repo-governance-standard.md +12 -1
- package/references/review-routing-standard.md +7 -1
- package/references/ssot-governance.md +67 -59
- package/references/taskr-gap-analysis.md +600 -0
- package/references/testing-standard.md +50 -0
- package/references/walkthrough-closeout.md +10 -9
- package/scripts/check-harness.mjs +111 -331
- package/scripts/commands/dashboard-command.mjs +67 -0
- package/scripts/commands/migration-command.mjs +96 -0
- package/scripts/commands/preset-command.mjs +73 -0
- package/scripts/commands/task-command.mjs +327 -0
- package/scripts/harness.mjs +106 -20
- package/scripts/lib/capability-registry.mjs +591 -0
- package/scripts/lib/check-module-parallel.mjs +237 -0
- package/scripts/lib/check-profiles.mjs +418 -0
- package/scripts/lib/check-task-contracts.mjs +47 -0
- package/scripts/lib/core-shared.mjs +196 -0
- package/scripts/lib/dashboard-data.mjs +412 -0
- package/scripts/lib/dashboard-workbench.mjs +257 -0
- package/scripts/lib/dashboard-writer.mjs +107 -4
- package/scripts/lib/git-status-summary.mjs +46 -0
- package/scripts/lib/governance-index-generator.mjs +174 -0
- package/scripts/lib/governance-sync.mjs +514 -0
- package/scripts/lib/governance-table-boundary.mjs +175 -0
- package/scripts/lib/harness-core.mjs +15 -1318
- package/scripts/lib/lesson-maintenance.mjs +152 -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/preset-audit-contracts.mjs +37 -0
- package/scripts/lib/preset-engine.mjs +497 -0
- package/scripts/lib/preset-registry.mjs +627 -0
- package/scripts/lib/preset-resource-contracts.mjs +83 -0
- package/scripts/lib/review-confirm-git-gate.mjs +248 -0
- package/scripts/lib/status-dashboard-renderer.mjs +102 -0
- package/scripts/lib/subagent-authorization-audit.mjs +196 -0
- package/scripts/lib/task-completion-consistency.mjs +16 -0
- package/scripts/lib/task-index.mjs +93 -0
- package/scripts/lib/task-lesson-candidates.mjs +242 -0
- package/scripts/lib/task-lesson-sedimentation.mjs +326 -0
- package/scripts/lib/task-lifecycle/review-confirm.mjs +101 -0
- package/scripts/lib/task-lifecycle/review-gates.mjs +70 -0
- package/scripts/lib/task-lifecycle/text-utils.mjs +24 -0
- package/scripts/lib/task-lifecycle.mjs +649 -0
- package/scripts/lib/task-review-model.mjs +469 -0
- package/scripts/lib/task-scanner.mjs +576 -0
- package/scripts/lib/task-tombstone-commands.mjs +140 -0
- package/scripts/postinstall.mjs +14 -0
- package/skills/preset-creator/SKILL.md +179 -0
- package/skills/preset-creator/references/complex-task-skeleton/README.md +31 -0
- package/skills/preset-creator/references/complex-task-skeleton/artifacts/INDEX.md +12 -0
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -0
- package/skills/preset-creator/references/complex-task-skeleton/execution_strategy.md +71 -0
- package/skills/preset-creator/references/complex-task-skeleton/findings.md +24 -0
- package/skills/preset-creator/references/complex-task-skeleton/lesson_candidates.md +70 -0
- package/skills/preset-creator/references/complex-task-skeleton/long-running-task-contract.md +76 -0
- package/skills/preset-creator/references/complex-task-skeleton/progress.md +33 -0
- package/skills/preset-creator/references/complex-task-skeleton/references/INDEX.md +13 -0
- package/skills/preset-creator/references/complex-task-skeleton/review.md +107 -0
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +111 -0
- package/{templates/planning/visual_roadmap.md → skills/preset-creator/references/complex-task-skeleton/visual_map.md} +24 -2
- package/skills/preset-creator/references/preset-package-skeleton.md +296 -0
- package/templates/AGENTS.md.template +51 -36
- 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 +42 -0
- package/templates/dashboard/assets/app-src/10-router.js +77 -0
- package/templates/dashboard/assets/app-src/20-overview.js +241 -0
- package/templates/dashboard/assets/app-src/30-tasks.js +409 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +246 -0
- package/templates/dashboard/assets/app-src/40-modules.js +58 -0
- package/templates/dashboard/assets/app-src/45-review.js +347 -0
- package/templates/dashboard/assets/app-src/50-migration.js +183 -0
- package/templates/dashboard/assets/app-src/60-shared.js +61 -0
- package/templates/dashboard/assets/app-src/90-bindings.js +524 -0
- package/templates/dashboard/assets/app.css +3107 -300
- package/templates/dashboard/assets/app.css.manifest.json +9 -0
- package/templates/dashboard/assets/app.js +2068 -306
- package/templates/dashboard/assets/app.manifest.json +12 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +342 -0
- package/templates/dashboard/assets/css-src/10-panels-flow.css +236 -0
- package/templates/dashboard/assets/css-src/20-briefs-controls.css +398 -0
- package/templates/dashboard/assets/css-src/30-task-index.css +739 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +507 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +427 -0
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +551 -0
- package/templates/dashboard/assets/i18n.js +531 -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/ledger/Harness-Ledger.md +13 -25
- package/templates/lessons/lesson-arch-process-change.md +1 -1
- package/templates/lessons/lesson-new-doc.md +1 -1
- package/templates/lessons/lesson-ref-change.md +1 -1
- package/templates/planning/brief.md +32 -0
- package/templates/planning/execution_strategy.md +31 -0
- package/templates/planning/lesson_candidates.md +70 -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/optional/artifacts/INDEX.md +3 -3
- package/templates/planning/optional/references/INDEX.md +3 -3
- package/templates/planning/review.md +59 -0
- package/templates/planning/task_plan.md +40 -15
- package/templates/planning/visual_map.md +50 -0
- package/templates/reference/docs-library-standard.md +31 -0
- package/templates/reference/execution-workflow-standard.md +5 -2
- package/templates/reference/external-source-intake-standard.md +82 -0
- package/templates/reference/harness-ledger-standard.md +1 -0
- package/templates/reference/pull-request-standard.md +80 -0
- package/templates/reference/repo-governance-standard.md +8 -5
- package/templates/reference/review-routing-standard.md +6 -0
- package/templates/reference/walkthrough-standard.md +3 -1
- package/templates/verifier/verifier-output.md +1 -1
- package/templates/walkthrough/walkthrough-template.md +2 -2
- package/templates-zh-CN/AGENTS.md.template +73 -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/ledger/Harness-Ledger.md +17 -40
- package/templates-zh-CN/planning/brief.md +32 -0
- package/templates-zh-CN/planning/execution_strategy.md +30 -0
- package/templates-zh-CN/planning/lesson_candidates.md +70 -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/review.md +59 -1
- package/templates-zh-CN/planning/task_plan.md +37 -11
- package/templates-zh-CN/planning/{visual_roadmap.md → visual_map.md} +21 -2
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/docs-library-standard.md +36 -1
- package/templates-zh-CN/reference/execution-workflow-standard.md +10 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +82 -0
- package/templates-zh-CN/reference/harness-ledger-standard.md +7 -4
- package/templates-zh-CN/reference/pull-request-standard.md +106 -0
- package/templates-zh-CN/reference/repo-governance-standard.md +4 -1
- package/templates-zh-CN/reference/review-routing-standard.md +8 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +6 -5
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +2 -2
- package/scripts/smoke-dashboard.mjs +0 -70
- package/scripts/test-harness.mjs +0 -483
- package/templates/ssot/Feature-SSoT.md +0 -43
- package/templates/ssot/Lessons-SSoT.md +0 -44
- 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
- package/templates-zh-CN/ssot/Feature-SSoT.md +0 -49
- package/templates-zh-CN/ssot/Lessons-SSoT.md +0 -49
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { spawnSync } from "node:child_process";
|
|
7
|
-
|
|
8
|
-
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
|
|
9
|
-
const node = process.execPath;
|
|
10
|
-
const cli = path.join(repoRoot, "scripts/harness.mjs");
|
|
11
|
-
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "harness-dashboard-smoke-"));
|
|
12
|
-
|
|
13
|
-
function run(args) {
|
|
14
|
-
const result = spawnSync(node, [cli, ...args], { cwd: repoRoot, encoding: "utf8" });
|
|
15
|
-
if (result.status !== 0) {
|
|
16
|
-
throw new Error(`${args.join(" ")} failed\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`);
|
|
17
|
-
}
|
|
18
|
-
return result;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function assert(condition, message) {
|
|
22
|
-
if (!condition) throw new Error(message);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function smokeTarget(target, name) {
|
|
26
|
-
const outDir = path.join(tmpRoot, name);
|
|
27
|
-
run(["dashboard", "--out-dir", outDir, target]);
|
|
28
|
-
for (const required of [
|
|
29
|
-
"index.html",
|
|
30
|
-
"assets/app.css",
|
|
31
|
-
"assets/app.js",
|
|
32
|
-
"assets/dashboard-data.js",
|
|
33
|
-
"data/status.json",
|
|
34
|
-
"data/tables.json",
|
|
35
|
-
"data/documents.json",
|
|
36
|
-
"data/graph.json",
|
|
37
|
-
"data/adoption.json",
|
|
38
|
-
]) {
|
|
39
|
-
assert(fs.existsSync(path.join(outDir, required)), `${name} missing ${required}`);
|
|
40
|
-
}
|
|
41
|
-
const index = fs.readFileSync(path.join(outDir, "index.html"), "utf8");
|
|
42
|
-
assert(index.includes("dashboard-data.js"), `${name} index missing data bootstrap`);
|
|
43
|
-
const payload = fs.readFileSync(path.join(outDir, "assets/dashboard-data.js"), "utf8");
|
|
44
|
-
assert(payload.includes("__HARNESS_DASHBOARD__"), `${name} missing embedded bundle`);
|
|
45
|
-
const documents = fs.readFileSync(path.join(outDir, "data/documents.json"), "utf8");
|
|
46
|
-
assert(!documents.includes(repoRoot), `${name} leaked repo absolute path`);
|
|
47
|
-
for (const generated of ["data/status.json", "data/tables.json", "data/documents.json", "data/graph.json", "data/adoption.json", "assets/dashboard-data.js"]) {
|
|
48
|
-
const content = fs.readFileSync(path.join(outDir, generated), "utf8");
|
|
49
|
-
assert(!content.includes("/Users/lizeyu"), `${name} ${generated} leaked local user path`);
|
|
50
|
-
assert(!content.includes("file://"), `${name} ${generated} leaked file URL`);
|
|
51
|
-
}
|
|
52
|
-
const docs = JSON.parse(fs.readFileSync(path.join(outDir, "data/documents.json"), "utf8"));
|
|
53
|
-
const tables = JSON.parse(fs.readFileSync(path.join(outDir, "data/tables.json"), "utf8"));
|
|
54
|
-
assert(!JSON.stringify(docs.documents.map((doc) => doc.path)).includes("_task-template"), `${name} documents included task template paths`);
|
|
55
|
-
assert(!JSON.stringify(tables.tables.map((table) => table.source)).includes("_task-template"), `${name} tables included task template sources`);
|
|
56
|
-
return outDir;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
smokeTarget("examples/minimal-project", "example");
|
|
60
|
-
|
|
61
|
-
const mingjingDocs = "/Users/lizeyu/Projects/mingjing-app/docs";
|
|
62
|
-
if (fs.existsSync(mingjingDocs)) {
|
|
63
|
-
const mingjingRepo = path.dirname(mingjingDocs);
|
|
64
|
-
const before = spawnSync("git", ["-C", mingjingRepo, "status", "--short", "--", "docs"], { encoding: "utf8" }).stdout;
|
|
65
|
-
smokeTarget(mingjingDocs, "mingjing");
|
|
66
|
-
const after = spawnSync("git", ["-C", mingjingRepo, "status", "--short", "--", "docs"], { encoding: "utf8" }).stdout;
|
|
67
|
-
assert(before === after, "Mingjing docs changed during dashboard smoke");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
console.log(`Dashboard smoke passed: ${tmpRoot}`);
|
package/scripts/test-harness.mjs
DELETED
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
import { spawnSync } from "node:child_process";
|
|
7
|
-
|
|
8
|
-
const repoRoot = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
|
|
9
|
-
const packageVersion = JSON.parse(fs.readFileSync(path.join(repoRoot, "package.json"), "utf8")).version;
|
|
10
|
-
const node = process.execPath;
|
|
11
|
-
const cli = path.join(repoRoot, "scripts/harness.mjs");
|
|
12
|
-
const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "harness-v1-"));
|
|
13
|
-
const chineseCharacterPattern = /\p{Script=Han}/u;
|
|
14
|
-
const brokenMechanicalTemplatePattern = /\bfill in(?:[A-Z]|\w)|(?:[a-z])fill in\b|TODO/;
|
|
15
|
-
const staleDispositionPattern = /\b((?:open\s*\/\s*)?fixed\s*\/\s*accepted\s*\/\s*deferred\s*\/\s*n\/a|accepted[- ]residuals?|accepted\s+(?:with|as)\s+residual|accepted\s+by\s+owner|accepted\s+waiver)\b/i;
|
|
16
|
-
const sampleOpenFindingPattern = /^\|\s*(?:F|R|SR|V|RR|HL)-\d+\s*\|.*\|\s*(?:open|yes\s*\|\s*open|yes\s*\/\s*no\s*\|\s*open)\s*\|?\s*$/im;
|
|
17
|
-
const englishFirstZhHeadingPattern = /^#{1,6}\s+(?:Reviewer Identity|Confidence Challenge|Material Findings|Non-Material Notes|Evidence Checked|Final Confidence Basis|Follow-Up Routing|Phase Graph|Phase Table|Context Packet|Artifact Index|Stop Condition|Pause Conditions|Deliverables|Module Session Prompt|Subagent\s*\/\s*Worker|Coordinator|Worktree|Slice ID|Parent Phase|Inputs|Verifier\b|Harness\b|Closeout\b|Lessons\b)/m;
|
|
18
|
-
const zhMechanicalEnglishWorkflowPattern = /^\s*\d+\.\s*(?:implement|run locally|self-review|rerun evidence)\b/im;
|
|
19
|
-
const zhMechanicalEvidencePhrasePattern = /\b(?:local smoke|browser or UI inspection|live environment smoke|reviewer findings|PR checks\s*\/\s*workflow run)\b/i;
|
|
20
|
-
|
|
21
|
-
function run(args, options = {}) {
|
|
22
|
-
const result = spawnSync(node, [cli, ...args], {
|
|
23
|
-
cwd: repoRoot,
|
|
24
|
-
encoding: "utf8",
|
|
25
|
-
...options,
|
|
26
|
-
});
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function assert(condition, message) {
|
|
31
|
-
if (!condition) throw new Error(message);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function expectPass(args) {
|
|
35
|
-
const result = run(args);
|
|
36
|
-
assert(result.status === 0, `${args.join(" ")} failed\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`);
|
|
37
|
-
return result;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function expectJson(args) {
|
|
41
|
-
const result = expectPass(args);
|
|
42
|
-
return JSON.parse(result.stdout);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function commandExists(command) {
|
|
46
|
-
const result = spawnSync(command, ["-v"], { encoding: "utf8" });
|
|
47
|
-
return !result.error && result.status === 0;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function runInTty(args, options = {}) {
|
|
51
|
-
const input = options.input || "";
|
|
52
|
-
const timeout = options.timeout;
|
|
53
|
-
const expectLines = [
|
|
54
|
-
`set timeout ${Math.ceil((timeout || 5000) / 1000)}`,
|
|
55
|
-
`spawn ${[node, cli, ...args].map(tclWord).join(" ")}`,
|
|
56
|
-
];
|
|
57
|
-
if (input) {
|
|
58
|
-
expectLines.push("expect -re {Language \\[1/2}");
|
|
59
|
-
expectLines.push(`send -- ${tclWord(input.replace(/\n/g, "\r"))}`);
|
|
60
|
-
}
|
|
61
|
-
expectLines.push("expect eof");
|
|
62
|
-
expectLines.push("catch wait result");
|
|
63
|
-
expectLines.push("exit [lindex $result 3]");
|
|
64
|
-
return spawnSync("expect", ["-c", expectLines.join("\n")], {
|
|
65
|
-
cwd: repoRoot,
|
|
66
|
-
encoding: "utf8",
|
|
67
|
-
timeout,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function expectTtyJson(args, options = {}) {
|
|
72
|
-
const result = runInTty(args, options);
|
|
73
|
-
assert(result.status === 0, `tty ${args.join(" ")} failed\nSTDOUT:\n${result.stdout}\nSTDERR:\n${result.stderr}`);
|
|
74
|
-
return parseJsonFromOutput(result.stdout);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function parseJsonFromOutput(output) {
|
|
78
|
-
const start = output.indexOf("{");
|
|
79
|
-
const end = output.lastIndexOf("}");
|
|
80
|
-
assert(start >= 0 && end > start, `output did not contain JSON\n${output}`);
|
|
81
|
-
return JSON.parse(output.slice(start, end + 1));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function tclWord(value) {
|
|
85
|
-
return `{${String(value).replace(/\\/g, "\\\\").replace(/}/g, "\\}")}}`;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
expectPass(["check", "--profile", "source-package", "."]);
|
|
89
|
-
if (fs.existsSync(path.join(repoRoot, ".harness-private"))) {
|
|
90
|
-
expectPass(["check", "--profile", "private-harness", ".harness-private"]);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const englishTemplateFiles = relativeFiles(path.join(repoRoot, "templates"));
|
|
94
|
-
const chineseTemplateFiles = relativeFiles(path.join(repoRoot, "templates-zh-CN"));
|
|
95
|
-
assert(englishTemplateFiles.length > 0, "templates/ should contain English templates");
|
|
96
|
-
assert(chineseTemplateFiles.length > 0, "templates-zh-CN/ should contain Chinese templates");
|
|
97
|
-
assert(
|
|
98
|
-
JSON.stringify(englishTemplateFiles) === JSON.stringify(chineseTemplateFiles),
|
|
99
|
-
"templates/ and templates-zh-CN/ should expose the same template file set",
|
|
100
|
-
);
|
|
101
|
-
for (const relativeFile of englishTemplateFiles) {
|
|
102
|
-
const content = fs.readFileSync(path.join(repoRoot, "templates", relativeFile), "utf8");
|
|
103
|
-
assert(!chineseCharacterPattern.test(content), `English template contains Chinese text: ${relativeFile}`);
|
|
104
|
-
assert(!brokenMechanicalTemplatePattern.test(content), `English template contains mechanical placeholder text: ${relativeFile}`);
|
|
105
|
-
assert(!staleDispositionPattern.test(content), `English template contains stale disposition vocabulary: ${relativeFile}`);
|
|
106
|
-
assert(!sampleOpenFindingPattern.test(content), `English template contains a real open sample finding row: ${relativeFile}`);
|
|
107
|
-
}
|
|
108
|
-
assert(
|
|
109
|
-
fs.readFileSync(path.join(repoRoot, "templates-zh-CN", "AGENTS.md.template"), "utf8").includes("项目概况"),
|
|
110
|
-
"templates-zh-CN should provide Chinese AGENTS.md content",
|
|
111
|
-
);
|
|
112
|
-
for (const relativeFile of chineseTemplateFiles) {
|
|
113
|
-
const content = fs.readFileSync(path.join(repoRoot, "templates-zh-CN", relativeFile), "utf8");
|
|
114
|
-
assert(!brokenMechanicalTemplatePattern.test(content), `Chinese template contains mechanical placeholder text: ${relativeFile}`);
|
|
115
|
-
assert(!staleDispositionPattern.test(content), `Chinese template contains stale disposition vocabulary: ${relativeFile}`);
|
|
116
|
-
assert(!sampleOpenFindingPattern.test(content), `Chinese template contains a real open sample finding row: ${relativeFile}`);
|
|
117
|
-
assert(!englishFirstZhHeadingPattern.test(content), `Chinese template contains English-first review heading: ${relativeFile}`);
|
|
118
|
-
assert(!zhMechanicalEnglishWorkflowPattern.test(content), `Chinese template contains unlocalized workflow phrase: ${relativeFile}`);
|
|
119
|
-
assert(!zhMechanicalEvidencePhrasePattern.test(content), `Chinese template contains unlocalized evidence phrase: ${relativeFile}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const exampleStatus = expectJson(["status", "--json", "examples/minimal-project"]);
|
|
123
|
-
assert(exampleStatus.project.name === "minimal-project", "example status project name mismatch");
|
|
124
|
-
assert(Array.isArray(exampleStatus.tasks), "example status missing tasks array");
|
|
125
|
-
assert(exampleStatus.tasks[0].state === "in_progress", "task state was not normalized");
|
|
126
|
-
assert(Array.isArray(exampleStatus.tasks[0].phases[0].requiredEvidence), "requiredEvidence must be an array");
|
|
127
|
-
assert(exampleStatus.capabilities.some((capability) => capability.name === "core"), "example status missing core capability");
|
|
128
|
-
|
|
129
|
-
const dashboardPath = path.join(tmpRoot, "dashboard.html");
|
|
130
|
-
expectPass(["dashboard", "--out", dashboardPath, "examples/minimal-project"]);
|
|
131
|
-
assert(fs.existsSync(dashboardPath), "dashboard file was not created");
|
|
132
|
-
const dashboardHtml = fs.readFileSync(dashboardPath, "utf8");
|
|
133
|
-
assert(dashboardHtml.includes("Harness Dashboard"), "dashboard HTML missing title");
|
|
134
|
-
assert(dashboardHtml.includes("Evidence"), "dashboard HTML missing evidence section");
|
|
135
|
-
assert(dashboardHtml.includes("Recent Activity"), "dashboard HTML missing recent activity section");
|
|
136
|
-
|
|
137
|
-
const dashboardDir = path.join(tmpRoot, "dashboard-folder");
|
|
138
|
-
expectPass(["dashboard", "--out-dir", dashboardDir, "examples/minimal-project"]);
|
|
139
|
-
for (const required of [
|
|
140
|
-
"index.html",
|
|
141
|
-
"assets/app.css",
|
|
142
|
-
"assets/app.js",
|
|
143
|
-
"assets/i18n.js",
|
|
144
|
-
"assets/markdown-reader.js",
|
|
145
|
-
"assets/mermaid-renderer.js",
|
|
146
|
-
"assets/dashboard-data.js",
|
|
147
|
-
"data/status.json",
|
|
148
|
-
"data/tables.json",
|
|
149
|
-
"data/documents.json",
|
|
150
|
-
"data/graph.json",
|
|
151
|
-
"data/adoption.json",
|
|
152
|
-
]) {
|
|
153
|
-
assert(fs.existsSync(path.join(dashboardDir, required)), `dashboard folder missing ${required}`);
|
|
154
|
-
}
|
|
155
|
-
const folderIndex = fs.readFileSync(path.join(dashboardDir, "index.html"), "utf8");
|
|
156
|
-
assert(folderIndex.includes("dashboard-data.js"), "dashboard folder index missing embedded data script");
|
|
157
|
-
assert(folderIndex.includes("rel=\"icon\""), "dashboard index should suppress favicon request");
|
|
158
|
-
const folderStatus = JSON.parse(fs.readFileSync(path.join(dashboardDir, "data/status.json"), "utf8"));
|
|
159
|
-
assert(folderStatus.tasks[0].roadmapSource === "standalone", "folder status should use standalone visual_roadmap.md");
|
|
160
|
-
const documents = JSON.parse(fs.readFileSync(path.join(dashboardDir, "data/documents.json"), "utf8"));
|
|
161
|
-
assert(documents.documents.some((doc) => doc.path.endsWith("execution_strategy.md")), "documents missing execution strategy");
|
|
162
|
-
assert(documents.documents.some((doc) => doc.path.endsWith("visual_roadmap.md")), "documents missing visual roadmap");
|
|
163
|
-
const tables = JSON.parse(fs.readFileSync(path.join(dashboardDir, "data/tables.json"), "utf8"));
|
|
164
|
-
assert(tables.tables.some((table) => table.kind === "harness-ledger"), "documents missing harness ledger table");
|
|
165
|
-
assert(JSON.stringify(tables).includes("alpha|beta"), "markdown table parser should preserve escaped pipes");
|
|
166
|
-
const graph = JSON.parse(fs.readFileSync(path.join(dashboardDir, "data/graph.json"), "utf8"));
|
|
167
|
-
assert(graph.edges.length > 0, "graph should include task/phase edges");
|
|
168
|
-
assertGraphIntegrity(graph, "example graph");
|
|
169
|
-
const dashboardApp = fs.readFileSync(path.join(dashboardDir, "assets/app.js"), "utf8");
|
|
170
|
-
const dashboardMarkdown = fs.readFileSync(path.join(dashboardDir, "assets/markdown-reader.js"), "utf8");
|
|
171
|
-
const dashboardMermaid = fs.readFileSync(path.join(dashboardDir, "assets/mermaid-renderer.js"), "utf8");
|
|
172
|
-
assert(dashboardApp.includes("data-render-mode"), "dashboard missing render/source toggle");
|
|
173
|
-
assert(dashboardApp.includes("escapeHtml(pageTitle())"), "dashboard page title must be escaped");
|
|
174
|
-
assert(dashboardMarkdown.includes("rendered-table"), "dashboard missing rendered markdown table support");
|
|
175
|
-
assert(dashboardMermaid.includes("mermaid-rendered"), "dashboard missing rendered mermaid output");
|
|
176
|
-
for (const generated of ["data/status.json", "data/tables.json", "data/documents.json", "data/graph.json", "data/adoption.json", "assets/dashboard-data.js"]) {
|
|
177
|
-
const content = fs.readFileSync(path.join(dashboardDir, generated), "utf8");
|
|
178
|
-
assert(!content.includes(repoRoot), `${generated} leaked absolute repo path`);
|
|
179
|
-
assert(!content.includes("file://"), `${generated} leaked file URL`);
|
|
180
|
-
assert(!hasLocalAbsolutePath(content), `${generated} leaked local absolute path`);
|
|
181
|
-
}
|
|
182
|
-
assert(!JSON.stringify(documents.documents.map((doc) => doc.path)).includes("_task-template"), "documents included task template paths");
|
|
183
|
-
|
|
184
|
-
const unsafeOut = run(["dashboard", "--out-dir", ".", "examples/minimal-project"]);
|
|
185
|
-
assert(unsafeOut.status !== 0, "dashboard --out-dir . should be refused");
|
|
186
|
-
const unsafeDocsOut = run(["dashboard", "--out-dir", "examples/minimal-project/docs", "examples/minimal-project"]);
|
|
187
|
-
assert(unsafeDocsOut.status !== 0, "dashboard --out-dir target docs should be refused");
|
|
188
|
-
const unsafeDocsChildOut = run(["dashboard", "--out-dir", "examples/minimal-project/docs/generated-dashboard", "examples/minimal-project"]);
|
|
189
|
-
assert(unsafeDocsChildOut.status !== 0, "dashboard --out-dir inside target docs should be refused");
|
|
190
|
-
|
|
191
|
-
const redactionTarget = path.join(tmpRoot, "redaction-target");
|
|
192
|
-
fs.mkdirSync(path.join(redactionTarget, "docs/09-PLANNING/TASKS/path-check"), { recursive: true });
|
|
193
|
-
fs.writeFileSync(path.join(redactionTarget, "AGENTS.md"), "# AGENTS\n");
|
|
194
|
-
fs.writeFileSync(path.join(redactionTarget, "docs/09-PLANNING/TASKS/path-check/task_plan.md"), "# Path Check\n");
|
|
195
|
-
fs.writeFileSync(
|
|
196
|
-
path.join(redactionTarget, "docs/09-PLANNING/TASKS/path-check/progress.md"),
|
|
197
|
-
"# Progress\n\n## Status\n\nin_progress\n\ncommand:TARGET:logs/check.txt: touched /tmp/secret and C:\\Users\\name\\secret\n",
|
|
198
|
-
);
|
|
199
|
-
const redactionDir = path.join(tmpRoot, "redaction-dashboard");
|
|
200
|
-
expectPass(["dashboard", "--out-dir", redactionDir, redactionTarget]);
|
|
201
|
-
const redactionData = fs.readFileSync(path.join(redactionDir, "assets/dashboard-data.js"), "utf8");
|
|
202
|
-
assert(redactionData.includes("LOCAL_PATH_REDACTED"), "dashboard data should include redacted local paths");
|
|
203
|
-
assert(!hasLocalAbsolutePath(redactionData), "dashboard data leaked generic local path");
|
|
204
|
-
|
|
205
|
-
const dryRunTarget = path.join(tmpRoot, "dry-run-target");
|
|
206
|
-
fs.mkdirSync(dryRunTarget);
|
|
207
|
-
const dryRun = expectJson(["init", "--dry-run", "--locale", "zh-CN", "--capabilities", "core,dashboard", dryRunTarget]);
|
|
208
|
-
assert(dryRun.dryRun === true, "init dry-run did not report dryRun true");
|
|
209
|
-
assert(dryRun.locale === "zh-CN", "init dry-run did not preserve zh-CN locale");
|
|
210
|
-
assert(!dryRun.changes.some((change) => change.destination.startsWith("docs/11-REFERENCE/")), "init scaffold should not mechanically copy reference standards");
|
|
211
|
-
assert(
|
|
212
|
-
dryRun.changes.some((change) => change.source === "templates-zh-CN/planning/task_plan.md"),
|
|
213
|
-
"init zh-CN dry-run should use localized task_plan template when available",
|
|
214
|
-
);
|
|
215
|
-
assert(!fs.existsSync(path.join(dryRunTarget, "AGENTS.md")), "init dry-run mutated target");
|
|
216
|
-
|
|
217
|
-
const nonInteractiveDefaultTarget = path.join(tmpRoot, "non-interactive-default-target");
|
|
218
|
-
fs.mkdirSync(nonInteractiveDefaultTarget);
|
|
219
|
-
const nonInteractiveDefault = expectJson(["init", "--dry-run", "--capabilities", "core", nonInteractiveDefaultTarget]);
|
|
220
|
-
assert(nonInteractiveDefault.locale === "en-US", "non-interactive init without --locale should default to en-US");
|
|
221
|
-
|
|
222
|
-
if (commandExists("expect")) {
|
|
223
|
-
const interactiveZhTarget = path.join(tmpRoot, "interactive-zh-target");
|
|
224
|
-
fs.mkdirSync(interactiveZhTarget);
|
|
225
|
-
const interactiveZh = expectTtyJson(["init", "--dry-run", "--capabilities", "core,dashboard", interactiveZhTarget], { input: "1\n", timeout: 5000 });
|
|
226
|
-
assert(interactiveZh.locale === "zh-CN", "interactive init option 1 should select zh-CN");
|
|
227
|
-
assert(
|
|
228
|
-
interactiveZh.changes.some((change) => change.source === "templates-zh-CN/planning/task_plan.md"),
|
|
229
|
-
"interactive zh-CN init should use localized templates",
|
|
230
|
-
);
|
|
231
|
-
|
|
232
|
-
const ttyExplicitTarget = path.join(tmpRoot, "tty-explicit-target");
|
|
233
|
-
fs.mkdirSync(ttyExplicitTarget);
|
|
234
|
-
const ttyExplicit = expectTtyJson(["init", "--dry-run", "--locale", "en-US", "--capabilities", "core", ttyExplicitTarget], { timeout: 5000 });
|
|
235
|
-
assert(ttyExplicit.locale === "en-US", "explicit --locale should win in TTY init");
|
|
236
|
-
} else {
|
|
237
|
-
console.log("Skipping TTY init tests: expect command is unavailable");
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const zhInitTarget = path.join(tmpRoot, "zh-init-target");
|
|
241
|
-
fs.mkdirSync(zhInitTarget);
|
|
242
|
-
const zhInit = expectJson(["init", "--locale", "zh-CN", "--capabilities", "core,dashboard", zhInitTarget]);
|
|
243
|
-
assert(zhInit.report?.locale === "zh-CN", "init output should include install report locale");
|
|
244
|
-
assert(zhInit.report?.capabilities?.some((capability) => capability.name === "core" && capability.default === true), "install report should explain core as default");
|
|
245
|
-
assert(zhInit.report?.capabilities?.some((capability) => capability.name === "dashboard" && capability.selected === true), "install report should mark selected capabilities");
|
|
246
|
-
assert(zhInit.report?.agentInstructions?.some((item) => item.includes("--locale")), "install report should remind agents to pass --locale explicitly");
|
|
247
|
-
const zhRegistry = JSON.parse(fs.readFileSync(path.join(zhInitTarget, ".harness-capabilities.json"), "utf8"));
|
|
248
|
-
assert(zhRegistry.locale === "zh-CN", "init should persist zh-CN locale");
|
|
249
|
-
assert(fs.readFileSync(path.join(zhInitTarget, "AGENTS.md"), "utf8").includes("项目概况"), "zh-CN init should write Chinese AGENTS.md");
|
|
250
|
-
const zhReviewTemplate = fs.readFileSync(path.join(zhInitTarget, "docs/09-PLANNING/TASKS/_task-template/review.md"), "utf8");
|
|
251
|
-
assert(zhReviewTemplate.includes("| ID | Severity | Finding | Evidence Checked | Required Action | Open | Disposition | Blocks Release | Follow-up |"), "zh-CN review template should preserve checker table headers");
|
|
252
|
-
const zhInitCheck = expectJson(["status", "--json", zhInitTarget]);
|
|
253
|
-
assert(zhInitCheck.checkState.status === "pass", "core+dashboard init should pass status check without safe-adoption");
|
|
254
|
-
assert(zhInitCheck.checkState.warnings === 0, "core+dashboard init should not warn about safe-adoption orphan artifacts");
|
|
255
|
-
const zhDashboardDir = path.join(tmpRoot, "zh-dashboard");
|
|
256
|
-
expectPass(["dashboard", "--out-dir", zhDashboardDir, zhInitTarget]);
|
|
257
|
-
const zhDashboardIndex = fs.readFileSync(path.join(zhDashboardDir, "index.html"), "utf8");
|
|
258
|
-
const zhDashboardApp = fs.readFileSync(path.join(zhDashboardDir, "assets/app.js"), "utf8");
|
|
259
|
-
assert(zhDashboardIndex.includes("Harness 控制台"), "zh-CN dashboard should use localized index template");
|
|
260
|
-
assert(zhDashboardApp.includes("项目驾驶舱"), "zh-CN dashboard should use localized app template");
|
|
261
|
-
|
|
262
|
-
const enRunTarget = path.join(tmpRoot, "en-run-target");
|
|
263
|
-
fs.mkdirSync(enRunTarget);
|
|
264
|
-
const enRun = expectJson(["init", "--dry-run", "--locale", "en-US", "--capabilities", "core", enRunTarget]);
|
|
265
|
-
assert(enRun.locale === "en-US", "init dry-run did not preserve en-US locale");
|
|
266
|
-
assert(
|
|
267
|
-
enRun.changes.some((change) => change.source === "templates/planning/task_plan.md"),
|
|
268
|
-
"init en-US dry-run should use default English task_plan template",
|
|
269
|
-
);
|
|
270
|
-
const enInitTarget = path.join(tmpRoot, "en-init-target");
|
|
271
|
-
fs.mkdirSync(enInitTarget);
|
|
272
|
-
expectJson(["init", "--locale", "en-US", "--capabilities", "core,dashboard", enInitTarget]);
|
|
273
|
-
const enInitStatus = expectJson(["status", "--json", enInitTarget]);
|
|
274
|
-
assert(enInitStatus.checkState.status === "pass", "en-US core+dashboard init should pass status check");
|
|
275
|
-
assert(enInitStatus.checkState.warnings === 0, "en-US core+dashboard init should not warn about safe-adoption");
|
|
276
|
-
|
|
277
|
-
const capTarget = path.join(tmpRoot, "cap-target");
|
|
278
|
-
fs.mkdirSync(capTarget);
|
|
279
|
-
expectPass(["add-capability", "dashboard", capTarget]);
|
|
280
|
-
const registry = JSON.parse(fs.readFileSync(path.join(capTarget, ".harness-capabilities.json"), "utf8"));
|
|
281
|
-
assert(registry.locale === "en-US", "add-capability registry missing default locale");
|
|
282
|
-
assert(registry.capabilities.some((capability) => capability.name === "dashboard"), "add-capability missing dashboard");
|
|
283
|
-
assert(registry.capabilities.some((capability) => capability.name === "core"), "add-capability missing dependency core");
|
|
284
|
-
const addReport = expectJson(["add-capability", "dashboard", "--dry-run", capTarget]);
|
|
285
|
-
assert(addReport.report?.capabilities?.some((capability) => capability.name === "dashboard"), "add-capability output should include install report");
|
|
286
|
-
|
|
287
|
-
const userInstallHome = path.join(tmpRoot, "user-install-home");
|
|
288
|
-
const userInstallDryRun = expectJson(["install-user", "--agent", "codex", "--home", userInstallHome, "--dry-run"]);
|
|
289
|
-
assert(userInstallDryRun.operation === "install-user", "install-user dry-run should report operation");
|
|
290
|
-
assert(userInstallDryRun.targets?.[0]?.agent === "codex", "install-user dry-run should target codex");
|
|
291
|
-
assert(userInstallDryRun.targets?.[0]?.changes?.some((change) => change.destination.endsWith("SKILL.md") && change.action === "would-create"), "install-user dry-run should plan SKILL.md");
|
|
292
|
-
assert(!fs.existsSync(path.join(userInstallHome, ".codex")), "install-user dry-run should not mutate home");
|
|
293
|
-
const userInstall = expectJson(["install-user", "--agent", "codex", "--home", userInstallHome, "--yes"]);
|
|
294
|
-
const codexSkillRoot = path.join(userInstallHome, ".codex/skills/coding-agent-harness");
|
|
295
|
-
assert(userInstall.status === "installed", "install-user should install skill");
|
|
296
|
-
assert(fs.existsSync(path.join(codexSkillRoot, "SKILL.md")), "install-user should copy SKILL.md");
|
|
297
|
-
assert(fs.existsSync(path.join(codexSkillRoot, "templates-zh-CN/AGENTS.md.template")), "install-user should copy Chinese templates");
|
|
298
|
-
assert(fs.existsSync(path.join(codexSkillRoot, "scripts/harness.mjs")), "install-user should copy CLI scripts");
|
|
299
|
-
assert(fs.existsSync(path.join(codexSkillRoot, "docs-release/guides/agent-installation.md")), "install-user should copy agent guide");
|
|
300
|
-
const userInstallAgain = expectJson(["install-user", "--agent", "codex", "--home", userInstallHome, "--yes"]);
|
|
301
|
-
assert(userInstallAgain.targets?.[0]?.changes?.some((change) => change.action === "skip-existing"), "install-user should not overwrite existing files by default");
|
|
302
|
-
const userDoctor = expectJson(["doctor-user", "--agent", "codex", "--home", userInstallHome]);
|
|
303
|
-
assert(userDoctor.status === "pass", "doctor-user should pass for installed codex skill");
|
|
304
|
-
assert(userDoctor.targets?.[0]?.version === packageVersion, "doctor-user should report installed package version");
|
|
305
|
-
const missingDoctor = run(["doctor-user", "--agent", "gemini", "--home", userInstallHome]);
|
|
306
|
-
assert(missingDoctor.status !== 0, "doctor-user should fail for missing agent install");
|
|
307
|
-
|
|
308
|
-
const zhCapTarget = path.join(tmpRoot, "zh-cap-target");
|
|
309
|
-
fs.mkdirSync(zhCapTarget);
|
|
310
|
-
expectPass(["add-capability", "dashboard", "--locale", "zh-CN", zhCapTarget]);
|
|
311
|
-
const zhCapRegistry = JSON.parse(fs.readFileSync(path.join(zhCapTarget, ".harness-capabilities.json"), "utf8"));
|
|
312
|
-
assert(zhCapRegistry.locale === "zh-CN", "add-capability should support zh-CN locale for legacy targets");
|
|
313
|
-
assert(fs.readFileSync(path.join(zhCapTarget, "AGENTS.md"), "utf8").includes("项目概况"), "zh-CN add-capability should write Chinese templates");
|
|
314
|
-
|
|
315
|
-
const mismatch = run(["init", "--capabilities", "core,module-parallel", capTarget]);
|
|
316
|
-
assert(mismatch.status !== 0, "init with mismatched existing capabilities should fail");
|
|
317
|
-
|
|
318
|
-
const invalidReviewTarget = path.join(tmpRoot, "invalid-review");
|
|
319
|
-
fs.mkdirSync(path.join(invalidReviewTarget, "docs/09-PLANNING/TASKS/bad"), { recursive: true });
|
|
320
|
-
fs.writeFileSync(
|
|
321
|
-
path.join(invalidReviewTarget, ".harness-capabilities.json"),
|
|
322
|
-
JSON.stringify({ version: 1, locale: "en-US", capabilities: [{ name: "core", state: "configured" }, { name: "adversarial-review", state: "configured" }] }, null, 2),
|
|
323
|
-
);
|
|
324
|
-
fs.writeFileSync(path.join(invalidReviewTarget, "docs/09-PLANNING/TASKS/bad/task_plan.md"), "# Bad\n");
|
|
325
|
-
fs.writeFileSync(
|
|
326
|
-
path.join(invalidReviewTarget, "docs/09-PLANNING/TASKS/bad/review.md"),
|
|
327
|
-
"# Review\n\n## Findings\n\n| ID | Severity | Finding | Evidence Checked | Required Action | Open | Disposition | Blocks Release | Follow-up |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n| R-001 | P1 | Missing sections | none | fix | no | mitigated | no | next |\n",
|
|
328
|
-
);
|
|
329
|
-
const invalidReview = run(["check", "--profile", "target-project", invalidReviewTarget]);
|
|
330
|
-
assert(invalidReview.status !== 0, "declared review missing required sections should fail");
|
|
331
|
-
|
|
332
|
-
const invalidVerifierTarget = path.join(tmpRoot, "invalid-verifier");
|
|
333
|
-
fs.mkdirSync(path.join(invalidVerifierTarget, "docs/09-PLANNING/TASKS/bad"), { recursive: true });
|
|
334
|
-
fs.writeFileSync(
|
|
335
|
-
path.join(invalidVerifierTarget, ".harness-capabilities.json"),
|
|
336
|
-
JSON.stringify({ version: 1, locale: "en-US", capabilities: [{ name: "core", state: "configured" }, { name: "adversarial-review", state: "configured" }] }, null, 2),
|
|
337
|
-
);
|
|
338
|
-
fs.writeFileSync(path.join(invalidVerifierTarget, "docs/09-PLANNING/TASKS/bad/task_plan.md"), "# Bad\n");
|
|
339
|
-
fs.writeFileSync(
|
|
340
|
-
path.join(invalidVerifierTarget, "docs/09-PLANNING/TASKS/bad/review.md"),
|
|
341
|
-
"# Review\n\n## Reviewer Identity\n\n| Reviewer | Type | Scope |\n| --- | --- | --- |\n| v1 | verifier | task |\n\n## Confidence Challenge\n\nVerifier reviewed this.\n\n## Evidence Checked\n\n| Evidence ID | Type | Path | Summary |\n| --- | --- | --- | --- |\n| E-001 | review | TARGET:docs/09-PLANNING/TASKS/bad/task_plan.md | checked |\n\n## Findings\n\n| ID | Severity | Finding | Evidence Checked | Required Action | Open | Disposition | Blocks Release | Follow-up |\n| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n| R-001 | P3 | Missing verifier schema | E-001 | fix | no | mitigated | no | next |\n\n## Final Confidence Basis\n\nexternal verifier reviewed this.\n",
|
|
342
|
-
);
|
|
343
|
-
const invalidVerifier = run(["check", "--profile", "target-project", invalidVerifierTarget]);
|
|
344
|
-
assert(invalidVerifier.status !== 0, "verifier review without template_id/verdict should fail");
|
|
345
|
-
|
|
346
|
-
const legacyContractTarget = path.join(tmpRoot, "legacy-contract");
|
|
347
|
-
fs.mkdirSync(path.join(legacyContractTarget, "docs/09-PLANNING/TASKS/old"), { recursive: true });
|
|
348
|
-
fs.writeFileSync(path.join(legacyContractTarget, "AGENTS.md"), "# AGENTS\n");
|
|
349
|
-
fs.writeFileSync(path.join(legacyContractTarget, "docs/09-PLANNING/TASKS/old/task_plan.md"), "# Old\n");
|
|
350
|
-
fs.writeFileSync(path.join(legacyContractTarget, "docs/09-PLANNING/TASKS/old/progress.md"), "# Progress\n\n## Status\n\nplanned\n");
|
|
351
|
-
const legacyLoose = run(["check", "--profile", "target-project", legacyContractTarget]);
|
|
352
|
-
assert(legacyLoose.status === 0, "legacy contract gaps should be advisory without strict");
|
|
353
|
-
const legacyStrict = run(["check", "--profile", "target-project", "--strict", legacyContractTarget]);
|
|
354
|
-
assert(legacyStrict.status !== 0, "strict legacy contract gaps should fail");
|
|
355
|
-
|
|
356
|
-
const legacyAdoptionTarget = path.join(tmpRoot, "legacy-adoption");
|
|
357
|
-
fs.mkdirSync(path.join(legacyAdoptionTarget, "docs/09-PLANNING/TASKS/old"), { recursive: true });
|
|
358
|
-
const legacyAgents = "# Legacy Agents\n\nLEGACY_DO_NOT_OVERWRITE\n";
|
|
359
|
-
const legacyClaude = "# Legacy Claude\n\nLEGACY_CLAUDE_DO_NOT_OVERWRITE\n";
|
|
360
|
-
const legacyLedger = "# Legacy Ledger\n\nLEGACY_LEDGER_DO_NOT_OVERWRITE\n";
|
|
361
|
-
const legacyTaskPlan = "# Legacy Task\n\nLEGACY_TASK_DO_NOT_OVERWRITE\n";
|
|
362
|
-
fs.writeFileSync(path.join(legacyAdoptionTarget, "AGENTS.md"), legacyAgents);
|
|
363
|
-
fs.writeFileSync(path.join(legacyAdoptionTarget, "CLAUDE.md"), legacyClaude);
|
|
364
|
-
fs.mkdirSync(path.join(legacyAdoptionTarget, "docs"), { recursive: true });
|
|
365
|
-
fs.writeFileSync(path.join(legacyAdoptionTarget, "docs/Harness-Ledger.md"), legacyLedger);
|
|
366
|
-
fs.writeFileSync(path.join(legacyAdoptionTarget, "docs/09-PLANNING/TASKS/old/task_plan.md"), legacyTaskPlan);
|
|
367
|
-
fs.writeFileSync(path.join(legacyAdoptionTarget, "docs/09-PLANNING/TASKS/old/progress.md"), "# Progress\n\n## Status\n\nplanned\n");
|
|
368
|
-
const legacyAdoption = expectJson(["add-capability", "safe-adoption", "--locale", "zh-CN", legacyAdoptionTarget]);
|
|
369
|
-
assert(legacyAdoption.report?.operation === "add-capability", "safe-adoption output should include add-capability report");
|
|
370
|
-
assert(
|
|
371
|
-
legacyAdoption.report?.capabilities?.some((capability) => capability.name === "safe-adoption" && capability.selected === true),
|
|
372
|
-
"safe-adoption report should mark safe-adoption selected",
|
|
373
|
-
);
|
|
374
|
-
assert(
|
|
375
|
-
legacyAdoption.report?.skipped?.includes("AGENTS.md") &&
|
|
376
|
-
legacyAdoption.report?.skipped?.includes("CLAUDE.md") &&
|
|
377
|
-
legacyAdoption.report?.skipped?.includes("docs/Harness-Ledger.md"),
|
|
378
|
-
"safe-adoption report should show skipped legacy files",
|
|
379
|
-
);
|
|
380
|
-
const legacyAdoptionRegistry = JSON.parse(fs.readFileSync(path.join(legacyAdoptionTarget, ".harness-capabilities.json"), "utf8"));
|
|
381
|
-
assert(legacyAdoptionRegistry.locale === "zh-CN", "safe-adoption should persist requested locale");
|
|
382
|
-
assert(legacyAdoptionRegistry.capabilities.some((capability) => capability.name === "core"), "safe-adoption should include core dependency");
|
|
383
|
-
assert(legacyAdoptionRegistry.capabilities.some((capability) => capability.name === "safe-adoption"), "safe-adoption registry missing capability");
|
|
384
|
-
assert(fs.readFileSync(path.join(legacyAdoptionTarget, "AGENTS.md"), "utf8") === legacyAgents, "safe-adoption should not overwrite legacy AGENTS.md");
|
|
385
|
-
assert(fs.readFileSync(path.join(legacyAdoptionTarget, "CLAUDE.md"), "utf8") === legacyClaude, "safe-adoption should not overwrite legacy CLAUDE.md");
|
|
386
|
-
assert(fs.readFileSync(path.join(legacyAdoptionTarget, "docs/Harness-Ledger.md"), "utf8") === legacyLedger, "safe-adoption should not overwrite legacy ledger");
|
|
387
|
-
assert(fs.readFileSync(path.join(legacyAdoptionTarget, "docs/09-PLANNING/TASKS/old/task_plan.md"), "utf8") === legacyTaskPlan, "safe-adoption should not overwrite old task plans");
|
|
388
|
-
assert(
|
|
389
|
-
fs.readFileSync(path.join(legacyAdoptionTarget, "docs/09-PLANNING/TASKS/_task-template/review.md"), "utf8").includes("审查者身份"),
|
|
390
|
-
"safe-adoption should add missing localized v1 templates",
|
|
391
|
-
);
|
|
392
|
-
const adoptedStatus = expectJson(["status", "--json", legacyAdoptionTarget]);
|
|
393
|
-
assert(adoptedStatus.checkState.status === "warn", "safe-adoption should warn on historical contract gaps without failing");
|
|
394
|
-
assert(
|
|
395
|
-
adoptedStatus.checkState.details.warnings.some((warning) => warning.includes("adoption-needed")),
|
|
396
|
-
"safe-adoption warnings should be routed as adoption-needed",
|
|
397
|
-
);
|
|
398
|
-
const adoptedStrict = run(["status", "--json", "--strict", legacyAdoptionTarget]);
|
|
399
|
-
assert(adoptedStrict.status !== 0, "safe-adoption strict status should still fail on historical contract gaps");
|
|
400
|
-
|
|
401
|
-
const legacyCheckerOnlyTarget = path.join(tmpRoot, "legacy-checker-only");
|
|
402
|
-
fs.mkdirSync(legacyCheckerOnlyTarget);
|
|
403
|
-
expectPass(["add-capability", "safe-adoption", "--locale", "en-US", legacyCheckerOnlyTarget]);
|
|
404
|
-
const legacyCheckerOnly = expectJson(["status", "--json", legacyCheckerOnlyTarget]);
|
|
405
|
-
assert(legacyCheckerOnly.checkState.status === "warn", "safe-adoption should surface legacy checker gaps as warnings");
|
|
406
|
-
assert(legacyCheckerOnly.checkState.legacy.status === "fail", "safe-adoption should keep legacy checker signal after registry creation");
|
|
407
|
-
const legacyCheckerOnlyStrictStatus = run(["status", "--json", "--strict", legacyCheckerOnlyTarget]);
|
|
408
|
-
assert(legacyCheckerOnlyStrictStatus.status !== 0, "safe-adoption strict status should fail when legacy checker fails even if v1 validators are clean");
|
|
409
|
-
const legacyCheckerOnlyStrictCheck = run(["check", "--profile", "target-project", "--strict", legacyCheckerOnlyTarget]);
|
|
410
|
-
assert(legacyCheckerOnlyStrictCheck.status !== 0, "safe-adoption strict check should fail when legacy checker fails even if v1 validators are clean");
|
|
411
|
-
|
|
412
|
-
const mingjingDocs = "/Users/lizeyu/Projects/mingjing-app/docs";
|
|
413
|
-
if (fs.existsSync(mingjingDocs)) {
|
|
414
|
-
const mingjingRepo = path.dirname(mingjingDocs);
|
|
415
|
-
const before = spawnSync("git", ["-C", mingjingRepo, "status", "--short", "--", "docs"], { encoding: "utf8" }).stdout;
|
|
416
|
-
const mingjing = run(["status", "--json", mingjingDocs]);
|
|
417
|
-
assert(mingjing.status === 0, "mingjing legacy status should be a safe-adoption warning, not a failure");
|
|
418
|
-
const status = JSON.parse(mingjing.stdout);
|
|
419
|
-
assert(status.project.docsOnly === true, "mingjing docs target was not detected as docsOnly");
|
|
420
|
-
assert(status.mode === "legacy-compat", "mingjing docs should be legacy-compat without capability registry");
|
|
421
|
-
assert(status.checkState.status === "warn", "mingjing legacy status should warn");
|
|
422
|
-
expectPass(["check", "--profile", "target-project", mingjingDocs]);
|
|
423
|
-
const strictStatus = run(["status", "--json", "--strict", mingjingDocs]);
|
|
424
|
-
const strictCheck = run(["check", "--profile", "target-project", "--strict", mingjingDocs]);
|
|
425
|
-
assert(strictStatus.status !== 0, "mingjing strict status should fail on legacy checker failures");
|
|
426
|
-
assert(strictCheck.status !== 0, "mingjing strict check should fail on legacy checker failures");
|
|
427
|
-
const mingjingDashboard = path.join(tmpRoot, "mingjing-dashboard.html");
|
|
428
|
-
expectPass(["dashboard", "--out", mingjingDashboard, mingjingDocs]);
|
|
429
|
-
assert(fs.existsSync(mingjingDashboard), "mingjing dashboard file was not created");
|
|
430
|
-
const mingjingDashboardDir = path.join(tmpRoot, "mingjing-dashboard-folder");
|
|
431
|
-
expectPass(["dashboard", "--out-dir", mingjingDashboardDir, mingjingDocs]);
|
|
432
|
-
assert(fs.existsSync(path.join(mingjingDashboardDir, "index.html")), "mingjing dashboard folder index was not created");
|
|
433
|
-
for (const generated of ["data/status.json", "data/tables.json", "data/documents.json", "data/graph.json", "data/adoption.json", "assets/dashboard-data.js"]) {
|
|
434
|
-
const content = fs.readFileSync(path.join(mingjingDashboardDir, generated), "utf8");
|
|
435
|
-
assert(!content.includes("/Users/lizeyu"), `mingjing ${generated} leaked local user path`);
|
|
436
|
-
assert(!content.includes("file://"), `mingjing ${generated} leaked file URL`);
|
|
437
|
-
}
|
|
438
|
-
const mingjingDocuments = JSON.parse(fs.readFileSync(path.join(mingjingDashboardDir, "data/documents.json"), "utf8"));
|
|
439
|
-
const mingjingTables = JSON.parse(fs.readFileSync(path.join(mingjingDashboardDir, "data/tables.json"), "utf8"));
|
|
440
|
-
assert(!JSON.stringify(mingjingDocuments.documents.map((doc) => doc.path)).includes("_task-template"), "mingjing documents included task template paths");
|
|
441
|
-
assert(!JSON.stringify(mingjingTables.tables.map((table) => table.source)).includes("_task-template"), "mingjing tables included task template sources");
|
|
442
|
-
const mingjingGraph = JSON.parse(fs.readFileSync(path.join(mingjingDashboardDir, "data/graph.json"), "utf8"));
|
|
443
|
-
assert(mingjingGraph.nodes.some((node) => node.type === "module"), "mingjing graph missing module nodes");
|
|
444
|
-
assert(mingjingGraph.edges.length > 0, "mingjing graph missing dependency edges");
|
|
445
|
-
assertGraphIntegrity(mingjingGraph, "mingjing graph");
|
|
446
|
-
const after = spawnSync("git", ["-C", mingjingRepo, "status", "--short", "--", "docs"], { encoding: "utf8" }).stdout;
|
|
447
|
-
assert(before === after, "mingjing docs changed during status/check/dashboard smoke");
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
console.log("Harness v1 tests passed");
|
|
451
|
-
|
|
452
|
-
function hasLocalAbsolutePath(content) {
|
|
453
|
-
return /(?:^|[\s"'(])(?:\/Users\/|\/Volumes\/|\/tmp\/|\/private\/tmp\/|\/var\/folders\/|\/home\/|[A-Za-z]:\\)/.test(content);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
function assertGraphIntegrity(graph, label) {
|
|
457
|
-
const nodes = new Set((graph.nodes || []).map((node) => node.id));
|
|
458
|
-
for (const edge of graph.edges || []) {
|
|
459
|
-
assert(nodes.has(edge.from), `${label} has dangling edge source ${edge.from}`);
|
|
460
|
-
assert(nodes.has(edge.to), `${label} has dangling edge target ${edge.to}`);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
function relativeFiles(root) {
|
|
465
|
-
const results = [];
|
|
466
|
-
function walk(dir) {
|
|
467
|
-
for (const entry of fs.readdirSync(dir)) {
|
|
468
|
-
const full = path.join(dir, entry);
|
|
469
|
-
const stat = fs.statSync(full);
|
|
470
|
-
if (stat.isDirectory()) {
|
|
471
|
-
walk(full);
|
|
472
|
-
} else {
|
|
473
|
-
results.push(toPosix(path.relative(root, full)));
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
walk(root);
|
|
478
|
-
return results.sort();
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
function toPosix(value) {
|
|
482
|
-
return value.split(path.sep).join("/");
|
|
483
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# Feature SSoT
|
|
2
|
-
|
|
3
|
-
## Purpose
|
|
4
|
-
|
|
5
|
-
Maintain the authoritative feature queue for agent-executed work. This file answers what is planned, who owns it, what evidence is required, and whether the work is safe to close.
|
|
6
|
-
|
|
7
|
-
## Status Legend
|
|
8
|
-
|
|
9
|
-
| Status | Meaning | Required Next Step |
|
|
10
|
-
| --- | --- | --- |
|
|
11
|
-
| intake | Candidate feature is captured but not shaped. | Clarify goal, owner, and acceptance evidence. |
|
|
12
|
-
| ready | Scope and acceptance criteria are clear. | Create or link the task plan. |
|
|
13
|
-
| active | Implementation or review is in progress. | Keep plan, evidence, and residuals current. |
|
|
14
|
-
| blocked | Feature cannot proceed. | Record blocker owner and unblock condition. |
|
|
15
|
-
| verify | Work is implemented and waiting for evidence. | Run required checks and update Regression SSoT. |
|
|
16
|
-
| shipped | Feature is delivered and walkthrough is complete. | Keep row until no longer operationally relevant. |
|
|
17
|
-
| archived | Feature is historical. | Keep archive pointer and final evidence link. |
|
|
18
|
-
|
|
19
|
-
## Active Features
|
|
20
|
-
|
|
21
|
-
| ID | Feature | User Outcome | Owner | Status | Priority | Task Plan | Acceptance Evidence | Regression Gate | Walkthrough | Residual | Updated |
|
|
22
|
-
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
|
|
23
|
-
| F-000 | Short feature title | Observable user or operator result | owner | ready | P1 | docs/09-PLANNING/TASKS/.../task_plan.md | command, test, screenshot, or review path | RG-000 | pending | none | YYYY-MM-DD |
|
|
24
|
-
|
|
25
|
-
## Intake Queue
|
|
26
|
-
|
|
27
|
-
| Candidate | Source | Decision Needed | Owner | Due |
|
|
28
|
-
| --- | --- | --- | --- | --- |
|
|
29
|
-
| Short request title | issue, roadmap, user request, or review | accept, split, defer, or reject | owner | YYYY-MM-DD |
|
|
30
|
-
|
|
31
|
-
## Routing Rules
|
|
32
|
-
|
|
33
|
-
1. Add a feature row before implementation starts unless the task is a documented emergency fix.
|
|
34
|
-
2. Use one row per deliverable outcome, not one row per commit.
|
|
35
|
-
3. Link the task plan before moving a feature to `active`.
|
|
36
|
-
4. Link regression evidence before moving a feature to `shipped`.
|
|
37
|
-
5. Record `accepted-risk` explicitly; `none` means no known residual risk after verification.
|
|
38
|
-
|
|
39
|
-
## Archive Rules
|
|
40
|
-
|
|
41
|
-
- Keep shipped rows visible through the next release or verification cycle.
|
|
42
|
-
- Move older shipped rows to the project archive with their final walkthrough and regression evidence.
|
|
43
|
-
- Do not archive blocked rows until the blocker is resolved, accepted, or explicitly abandoned.
|