coding-agent-harness 1.0.8 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/CONTRIBUTING.md +8 -4
- package/README.md +12 -2
- package/README.zh-CN.md +10 -2
- package/SKILL.md +14 -3
- package/dist/build-dist.mjs +19 -6
- package/dist/check-dist-observation.mjs +57 -29
- package/dist/check-harness.mjs +0 -1
- package/dist/check-import-graph.mjs +44 -27
- package/dist/check-lite-forbidden-surfaces.mjs +121 -0
- package/dist/check-no-ts-nocheck.mjs +7 -7
- package/dist/check-runtime-emit.mjs +10 -3
- package/dist/check-type-boundaries.mjs +51 -9
- package/dist/commands/dashboard-command.mjs +52 -14
- package/dist/commands/migration-command.mjs +18 -8
- package/dist/commands/module-command.mjs +142 -0
- package/dist/commands/preset-command.mjs +51 -12
- package/dist/commands/registry.mjs +483 -0
- package/dist/commands/task-command.mjs +109 -52
- package/dist/harness.mjs +6 -304
- package/dist/lib/capability-registry.mjs +229 -53
- package/dist/lib/check-module-parallel.mjs +1 -6
- package/dist/lib/check-profiles.mjs +39 -46
- package/dist/lib/check-task-contracts.mjs +6 -4
- package/dist/lib/command-registry.mjs +248 -0
- package/dist/lib/core-shared.mjs +78 -3
- package/dist/lib/dashboard-data.mjs +203 -22
- package/dist/lib/dashboard-workbench.mjs +245 -21
- package/dist/lib/dashboard-writer.mjs +4 -1
- package/dist/lib/git-status-summary.mjs +0 -1
- package/dist/lib/governance-index-generator.mjs +7 -5
- package/dist/lib/governance-sync.mjs +46 -121
- package/dist/lib/governance-table-boundary.mjs +1 -14
- package/dist/lib/harness-core.mjs +4 -1
- package/dist/lib/harness-paths.mjs +115 -1
- package/dist/lib/impact-classifier.mjs +420 -0
- package/dist/lib/lesson-maintenance.mjs +1 -2
- package/dist/lib/markdown-utils.mjs +50 -1
- package/dist/lib/migration-planner.mjs +31 -16
- package/dist/lib/migration-support.mjs +5 -4
- package/dist/lib/module-registry.mjs +296 -0
- package/dist/lib/preset-audit-contracts.mjs +24 -1
- package/dist/lib/preset-engine.mjs +67 -29
- package/dist/lib/preset-registry.mjs +361 -71
- package/dist/lib/preset-runner.mjs +292 -26
- package/dist/lib/review-confirm-git-gate.mjs +73 -19
- package/dist/lib/status-builder.mjs +23 -8
- package/dist/lib/structure-migration.mjs +6 -4
- package/dist/lib/subagent-authorization-audit.mjs +8 -2
- package/dist/lib/task-archive-eligibility.mjs +65 -0
- package/dist/lib/task-audit-metadata.mjs +25 -11
- package/dist/lib/task-audit-migration.mjs +21 -14
- package/dist/lib/task-discovery-contract.mjs +32 -0
- package/dist/lib/task-index.mjs +3 -2
- package/dist/lib/task-lesson-candidates.mjs +1 -2
- package/dist/lib/task-lesson-sedimentation.mjs +310 -9
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +6 -3
- package/dist/lib/task-lifecycle/phase-sync.mjs +0 -1
- package/dist/lib/task-lifecycle/preset-interop.mjs +16 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +34 -2
- package/dist/lib/task-lifecycle/review-gates.mjs +12 -5
- package/dist/lib/task-lifecycle/review-submission.mjs +1 -2
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +0 -1
- package/dist/lib/task-lifecycle/template-files.mjs +2 -5
- package/dist/lib/task-lifecycle.mjs +116 -160
- package/dist/lib/task-metadata.mjs +10 -5
- package/dist/lib/task-preset-contract-drift.mjs +45 -0
- package/dist/lib/task-repository.mjs +192 -0
- package/dist/lib/task-review-model.mjs +36 -17
- package/dist/lib/task-scanner.mjs +74 -23
- package/dist/lib/task-template-materials.mjs +131 -0
- package/dist/lib/task-tombstone-commands.mjs +186 -29
- package/dist/lib/types/check-profiles.js +1 -0
- package/dist/lib/types/impact.js +1 -0
- package/dist/lib/types/preset.js +1 -0
- package/dist/lib/types/task-lifecycle.js +1 -0
- package/dist/lib/types/task-scanner.js +1 -0
- package/dist/postinstall.mjs +2 -2
- package/dist/run-built-tests.mjs +10 -3
- package/docs-release/README.md +1 -0
- package/docs-release/architecture/document-contract-kernel/README.md +150 -0
- package/docs-release/architecture/document-contract-kernel/products/full-skill-overlay.md +29 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt +26 -0
- package/docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md +37 -0
- package/docs-release/architecture/overview.md +2 -2
- package/docs-release/architecture/overview.zh-CN.md +2 -2
- package/docs-release/architecture/system-explainer/01-system-overview.md +11 -7
- package/docs-release/architecture/system-explainer/02-module-dependency.md +4 -4
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +17 -12
- package/docs-release/architecture/system-explainer/05-data-flow.md +6 -6
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/README.md +1 -1
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +12 -8
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +19 -11
- package/docs-release/architecture/system-explainer/en-US/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/en-US/06-preset-and-migration.md +2 -2
- package/docs-release/architecture/system-explainer/en-US/README.md +1 -1
- package/docs-release/guides/agent-installation.en-US.md +4 -6
- package/docs-release/guides/agent-installation.md +11 -8
- package/docs-release/guides/contributing.md +10 -3
- package/docs-release/guides/contributing.zh-CN.md +10 -3
- package/docs-release/guides/legacy-migration-agent-prompt.md +1 -1
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +1 -1
- package/docs-release/guides/migration-playbook.en-US.md +9 -6
- package/docs-release/guides/migration-playbook.md +9 -6
- package/docs-release/guides/preset-development.md +68 -2
- package/docs-release/guides/task-state-machine.en-US.md +8 -8
- package/docs-release/guides/task-state-machine.md +7 -7
- package/docs-release/guides/typescript-runtime-migration-closeout.md +17 -13
- package/package.json +16 -12
- package/postinstall.mjs +37 -0
- package/presets/legacy-migration/preset.yaml +5 -5
- package/presets/legacy-migration/templates/execution_strategy.append.md +1 -1
- package/presets/lesson-sedimentation/preset.yaml +3 -3
- package/presets/module/preset.yaml +2 -2
- package/presets/module/templates/execution_strategy.append.md +1 -1
- package/presets/module/templates/task_plan.append.md +3 -3
- package/presets/release-closeout/checks/check-release-package.mjs +6 -1
- package/presets/release-closeout/preset.yaml +9 -9
- package/presets/release-closeout/scripts/generate-release-package.mjs +387 -25
- package/presets/release-closeout/templates/task_plan.append.md +5 -5
- package/presets/standard-task/preset.yaml +2 -2
- package/references/agents-md-pattern.md +23 -17
- package/references/lessons-governance.md +2 -2
- package/references/module-parallel-standard.md +3 -6
- package/references/ssot-governance.md +2 -2
- package/references/taskr-gap-analysis.md +3 -3
- package/run-dist.mjs +34 -0
- package/skills/preset-creator/SKILL.md +40 -8
- package/skills/preset-creator/references/complex-task-skeleton/brief.md +32 -8
- package/skills/preset-creator/references/preset-package-skeleton.md +15 -5
- package/skills/preset-creator/references/structure-aware-paths.md +112 -0
- package/templates/AGENTS.md.template +28 -26
- package/templates/architecture/README.md +2 -2
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/00-state.js +5 -1
- package/templates/dashboard/assets/app-src/10-router.js +7 -0
- package/templates/dashboard/assets/app-src/20-overview.js +8 -8
- package/templates/dashboard/assets/app-src/30-tasks.js +132 -40
- package/templates/dashboard/assets/app-src/32-task-swimlane.js +314 -0
- package/templates/dashboard/assets/app-src/35-task-detail.js +35 -5
- package/templates/dashboard/assets/app-src/40-modules.js +257 -41
- package/templates/dashboard/assets/app-src/45-review.js +127 -1
- package/templates/dashboard/assets/app-src/90-bindings.js +185 -2
- package/templates/dashboard/assets/app.css +928 -53
- package/templates/dashboard/assets/app.css.manifest.json +2 -0
- package/templates/dashboard/assets/app.js +1071 -98
- package/templates/dashboard/assets/app.manifest.json +1 -0
- package/templates/dashboard/assets/css-src/00-foundation.css +12 -6
- package/templates/dashboard/assets/css-src/10-panels-flow.css +2 -2
- package/templates/dashboard/assets/css-src/30-task-index.css +21 -13
- package/templates/dashboard/assets/css-src/31-archive.css +94 -0
- package/templates/dashboard/assets/css-src/32-task-swimlane.css +487 -0
- package/templates/dashboard/assets/css-src/35-review-workspace.css +78 -0
- package/templates/dashboard/assets/css-src/40-detail-modules-migration.css +191 -14
- package/templates/dashboard/assets/css-src/50-responsive-overrides.css +23 -0
- package/templates/dashboard/assets/i18n.js +166 -2
- package/templates/development/README.md +9 -9
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +1 -1
- package/templates/development/external-source-packs/README.md +2 -2
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +1 -1
- package/templates/integrations/event-contract.md +1 -1
- package/templates/integrations/third-party/vendor-template.md +1 -1
- package/templates/integrations/webhook-contract.md +1 -1
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/modules/module_brief.md +50 -0
- package/templates/modules/module_plan.md +49 -0
- package/templates/modules/registry_view.md +9 -0
- package/templates/modules/session_prompt_pack.md +55 -0
- package/templates/planning/brief.md +32 -8
- package/templates/planning/module_brief.md +28 -3
- package/templates/planning/module_plan.md +26 -11
- package/templates/planning/module_session_prompt.md +11 -2
- package/templates/planning/optional/slices/_slice-template/brief.md +28 -0
- package/templates/planning/review.md +1 -1
- package/templates/planning/visual_map.md +1 -1
- package/templates/reference/docs-library-standard.md +7 -7
- package/templates/reference/execution-workflow-standard.md +13 -0
- package/templates/reference/external-source-intake-standard.md +10 -10
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/reference/review-routing-standard.md +4 -0
- package/templates/ssot/Module-Registry.md +4 -38
- package/templates/walkthrough/walkthrough-template.md +1 -1
- package/templates-zh-CN/AGENTS.md.template +27 -25
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +2 -2
- package/templates-zh-CN/architecture/service-catalog.md +2 -2
- package/templates-zh-CN/architecture/services/service-template.md +1 -1
- package/templates-zh-CN/development/README.md +9 -9
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +1 -1
- package/templates-zh-CN/development/external-source-packs/README.md +2 -2
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +1 -1
- package/templates-zh-CN/integrations/event-contract.md +1 -1
- package/templates-zh-CN/integrations/third-party/vendor-template.md +1 -1
- package/templates-zh-CN/integrations/webhook-contract.md +1 -1
- package/templates-zh-CN/ledger/Harness-Ledger.md +1 -1
- package/templates-zh-CN/lessons/lesson-arch-process-change.md +1 -1
- package/templates-zh-CN/lessons/lesson-new-doc.md +3 -3
- package/templates-zh-CN/lessons/lesson-ref-change.md +4 -4
- package/templates-zh-CN/modules/module_brief.md +47 -0
- package/templates-zh-CN/modules/module_plan.md +48 -0
- package/templates-zh-CN/modules/registry_view.md +9 -0
- package/templates-zh-CN/modules/session_prompt_pack.md +50 -0
- package/templates-zh-CN/planning/INDEX.md +1 -0
- package/templates-zh-CN/planning/brief.md +26 -7
- package/templates-zh-CN/planning/module_brief.md +24 -2
- package/templates-zh-CN/planning/module_plan.md +35 -29
- package/templates-zh-CN/planning/module_session_prompt.md +15 -11
- package/templates-zh-CN/planning/optional/slices/_slice-template/brief.md +28 -11
- package/templates-zh-CN/planning/review.md +1 -1
- package/templates-zh-CN/reference/adversarial-review-standard.md +1 -1
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +27 -27
- package/templates-zh-CN/reference/execution-workflow-standard.md +12 -2
- package/templates-zh-CN/reference/external-source-intake-standard.md +10 -10
- package/templates-zh-CN/reference/harness-ledger-standard.md +3 -3
- package/templates-zh-CN/reference/regression-ssot-governance.md +2 -2
- package/templates-zh-CN/reference/repo-governance-standard.md +1 -1
- package/templates-zh-CN/reference/review-routing-standard.md +3 -0
- package/templates-zh-CN/reference/walkthrough-standard.md +2 -2
- package/templates-zh-CN/reference/worktree-standard.md +1 -1
- package/templates-zh-CN/regression/Cadence-Ledger.md +2 -2
- package/templates-zh-CN/ssot/Delivery-SSoT.md +2 -2
- package/templates-zh-CN/ssot/Module-Registry.md +5 -44
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +4 -4
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
|
+
function parseArgs(argv) {
|
|
7
|
+
let repoRoot = defaultRepoRoot;
|
|
8
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
9
|
+
const arg = argv[index];
|
|
10
|
+
if (arg === "--repo-root") {
|
|
11
|
+
const value = argv[index + 1];
|
|
12
|
+
if (!value)
|
|
13
|
+
throw new Error("--repo-root requires a value");
|
|
14
|
+
repoRoot = path.resolve(value);
|
|
15
|
+
index += 1;
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
19
|
+
}
|
|
20
|
+
return { repoRoot };
|
|
21
|
+
}
|
|
22
|
+
export function checkLiteForbiddenSurfaces(repoRoot = defaultRepoRoot) {
|
|
23
|
+
const forbiddenPath = path.join(repoRoot, "docs-release/architecture/document-contract-kernel/products/lite-forbidden-surfaces.txt");
|
|
24
|
+
const patterns = readForbiddenPatterns(forbiddenPath);
|
|
25
|
+
const scannedFiles = collectLiteProductFiles(repoRoot);
|
|
26
|
+
const violations = [];
|
|
27
|
+
for (const relativeFile of scannedFiles) {
|
|
28
|
+
const absoluteFile = path.join(repoRoot, relativeFile);
|
|
29
|
+
const lines = fs.readFileSync(absoluteFile, "utf8").split(/\r?\n/);
|
|
30
|
+
for (const [lineIndex, line] of lines.entries()) {
|
|
31
|
+
for (const pattern of patterns) {
|
|
32
|
+
pattern.pattern.lastIndex = 0;
|
|
33
|
+
if (pattern.pattern.test(line)) {
|
|
34
|
+
violations.push({
|
|
35
|
+
file: relativeFile,
|
|
36
|
+
line: lineIndex + 1,
|
|
37
|
+
pattern: pattern.source,
|
|
38
|
+
text: line.trim(),
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { ok: violations.length === 0, violations, scannedFiles };
|
|
45
|
+
}
|
|
46
|
+
function readForbiddenPatterns(forbiddenPath) {
|
|
47
|
+
if (!fs.existsSync(forbiddenPath)) {
|
|
48
|
+
throw new Error(`Missing Lite forbidden-surface list: ${path.relative(process.cwd(), forbiddenPath)}`);
|
|
49
|
+
}
|
|
50
|
+
const lines = fs.readFileSync(forbiddenPath, "utf8").split(/\r?\n/);
|
|
51
|
+
return lines
|
|
52
|
+
.map((line) => line.trim())
|
|
53
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
54
|
+
.map((line) => {
|
|
55
|
+
if (line.startsWith("literal:")) {
|
|
56
|
+
const literal = line.slice("literal:".length);
|
|
57
|
+
return { source: line, pattern: new RegExp(escapeRegExp(literal), "i") };
|
|
58
|
+
}
|
|
59
|
+
if (line.startsWith("regex:")) {
|
|
60
|
+
const source = line.slice("regex:".length);
|
|
61
|
+
return { source: line, pattern: new RegExp(source, "i") };
|
|
62
|
+
}
|
|
63
|
+
return { source: line, pattern: new RegExp(escapeRegExp(line), "i") };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function collectLiteProductFiles(repoRoot) {
|
|
67
|
+
const files = new Set();
|
|
68
|
+
const explicitFiles = [
|
|
69
|
+
"docs-release/architecture/document-contract-kernel/products/lite-skill-overlay.md",
|
|
70
|
+
"skills/coding-agent-harness-lite/SKILL.md",
|
|
71
|
+
];
|
|
72
|
+
for (const relativeFile of explicitFiles) {
|
|
73
|
+
if (fs.existsSync(path.join(repoRoot, relativeFile)))
|
|
74
|
+
files.add(relativeFile);
|
|
75
|
+
}
|
|
76
|
+
for (const relativeDir of ["skill-sources/products/lite", "skill-sources/document-kernel/products/lite"]) {
|
|
77
|
+
const absoluteDir = path.join(repoRoot, relativeDir);
|
|
78
|
+
if (!fs.existsSync(absoluteDir))
|
|
79
|
+
continue;
|
|
80
|
+
for (const relativeFile of walkTextFiles(absoluteDir, repoRoot))
|
|
81
|
+
files.add(relativeFile);
|
|
82
|
+
}
|
|
83
|
+
return [...files].sort();
|
|
84
|
+
}
|
|
85
|
+
function walkTextFiles(current, repoRoot) {
|
|
86
|
+
const stat = fs.lstatSync(current);
|
|
87
|
+
if (stat.isSymbolicLink())
|
|
88
|
+
return [];
|
|
89
|
+
if (stat.isFile()) {
|
|
90
|
+
return /\.(md|txt|template)$/.test(current) ? [toPosix(path.relative(repoRoot, current))] : [];
|
|
91
|
+
}
|
|
92
|
+
const files = [];
|
|
93
|
+
for (const entry of fs.readdirSync(current)) {
|
|
94
|
+
files.push(...walkTextFiles(path.join(current, entry), repoRoot));
|
|
95
|
+
}
|
|
96
|
+
return files;
|
|
97
|
+
}
|
|
98
|
+
function toPosix(value) {
|
|
99
|
+
return value.split(path.sep).join("/");
|
|
100
|
+
}
|
|
101
|
+
function escapeRegExp(value) {
|
|
102
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
103
|
+
}
|
|
104
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
105
|
+
try {
|
|
106
|
+
const { repoRoot } = parseArgs(process.argv.slice(2));
|
|
107
|
+
const result = checkLiteForbiddenSurfaces(repoRoot);
|
|
108
|
+
if (!result.ok) {
|
|
109
|
+
console.error([
|
|
110
|
+
"Lite forbidden-surface check failed:",
|
|
111
|
+
...result.violations.map((violation) => `${violation.file}:${violation.line}: ${violation.pattern}: ${violation.text}`),
|
|
112
|
+
].join("\n"));
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
console.log(`Lite forbidden-surface check passed (${result.scannedFiles.length} files scanned)`);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -4,9 +4,9 @@ import path from "node:path";
|
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
6
|
const sourceRoots = ["scripts", "tests"];
|
|
7
|
-
const tsNocheckPattern =
|
|
7
|
+
const tsNocheckPattern = new RegExp(String.raw `^\s*//\s*` + "@ts" + String.raw `-nocheck\b`);
|
|
8
8
|
export function checkNoTsNocheck({ repoRoot = defaultRepoRoot, allowlistPath = path.join(repoRoot, "scripts/ts-nocheck-allowlist.json"), } = {}) {
|
|
9
|
-
const files =
|
|
9
|
+
const files = collectTypeScriptFiles(repoRoot);
|
|
10
10
|
const allowlist = readAllowlist(allowlistPath);
|
|
11
11
|
const violations = [];
|
|
12
12
|
const observed = new Set();
|
|
@@ -22,7 +22,7 @@ export function checkNoTsNocheck({ repoRoot = defaultRepoRoot, allowlistPath = p
|
|
|
22
22
|
code: "unlisted-ts-nocheck",
|
|
23
23
|
file,
|
|
24
24
|
line: lineIndex + 1,
|
|
25
|
-
message: `${file}:${lineIndex + 1} has @ts-nocheck but is not in scripts/ts-nocheck-allowlist.json`,
|
|
25
|
+
message: `${file}:${lineIndex + 1} has ${"@ts"}-nocheck but is not in scripts/ts-nocheck-allowlist.json`,
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -31,13 +31,13 @@ export function checkNoTsNocheck({ repoRoot = defaultRepoRoot, allowlistPath = p
|
|
|
31
31
|
violations.push({
|
|
32
32
|
code: "stale-ts-nocheck-allowlist",
|
|
33
33
|
file,
|
|
34
|
-
message: `${file} is listed in scripts/ts-nocheck-allowlist.json but no longer has @ts-nocheck`,
|
|
34
|
+
message: `${file} is listed in scripts/ts-nocheck-allowlist.json but no longer has ${"@ts"}-nocheck`,
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
return { ok: violations.length === 0, violations };
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function collectTypeScriptFiles(repoRoot) {
|
|
41
41
|
const files = [];
|
|
42
42
|
for (const root of sourceRoots) {
|
|
43
43
|
const absoluteRoot = path.join(repoRoot, root);
|
|
@@ -59,7 +59,7 @@ function walk(current, files, repoRoot) {
|
|
|
59
59
|
walk(path.join(current, entry), files, repoRoot);
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
if (stat.isFile() &&
|
|
62
|
+
if (stat.isFile() && /\.(mts|ts)$/.test(current)) {
|
|
63
63
|
files.push(path.relative(repoRoot, current).split(path.sep).join("/"));
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -84,5 +84,5 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
|
84
84
|
console.error(result.violations.map((violation) => violation.message).join("\n"));
|
|
85
85
|
process.exit(1);
|
|
86
86
|
}
|
|
87
|
-
console.log(
|
|
87
|
+
console.log(`No ${"@ts"}-nocheck gate passed`);
|
|
88
88
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @ts-nocheck
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
@@ -75,11 +74,19 @@ export function checkRuntimeEmitContract({ projectRoot = defaultRepoRoot, config
|
|
|
75
74
|
};
|
|
76
75
|
}
|
|
77
76
|
function runTypeScriptEmit({ projectRoot, configPath, outDir }) {
|
|
78
|
-
|
|
77
|
+
const npmArgs = ["exec", "--yes", "--package", `typescript@${typescriptVersion}`, "--", "tsc", "-p", configPath, "--outDir", outDir, "--noCheck"];
|
|
78
|
+
const npmCommand = resolveNpmCommand(npmArgs);
|
|
79
|
+
return spawnSync(npmCommand.command, npmCommand.args, {
|
|
79
80
|
cwd: projectRoot,
|
|
80
81
|
encoding: "utf8",
|
|
81
82
|
});
|
|
82
83
|
}
|
|
84
|
+
function resolveNpmCommand(npmArgs) {
|
|
85
|
+
const npmExecPath = process.env.npm_execpath;
|
|
86
|
+
if (npmExecPath)
|
|
87
|
+
return { command: process.execPath, args: [npmExecPath, ...npmArgs] };
|
|
88
|
+
return { command: process.platform === "win32" ? "npm.cmd" : "npm", args: npmArgs };
|
|
89
|
+
}
|
|
83
90
|
function compareDirectories({ expectedDir, actualDir, violations }) {
|
|
84
91
|
const expectedFiles = collectFiles(expectedDir).filter((file) => file.endsWith(".mjs")).sort();
|
|
85
92
|
const actualFiles = collectFiles(actualDir).filter((file) => file.endsWith(".mjs")).sort();
|
|
@@ -129,7 +136,7 @@ function collectFiles(directory) {
|
|
|
129
136
|
walk(directory, files, () => true);
|
|
130
137
|
return files.sort();
|
|
131
138
|
}
|
|
132
|
-
function walk(current, files, predicate, sourceRoot) {
|
|
139
|
+
function walk(current, files, predicate, sourceRoot = "") {
|
|
133
140
|
const stat = fs.lstatSync(current);
|
|
134
141
|
if (stat.isSymbolicLink())
|
|
135
142
|
return;
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @ts-nocheck
|
|
3
2
|
import fs from "node:fs";
|
|
4
3
|
import path from "node:path";
|
|
5
4
|
import { fileURLToPath } from "node:url";
|
|
6
5
|
const defaultRepoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
7
6
|
const sourceRoots = ["scripts", "tests"];
|
|
8
7
|
const importPattern = /\b(import|export)\s+(type\s+)?(?:[^'"]*?\s+from\s+)?["']([^"']+)["']|\bimport\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
9
|
-
const tsEscapePattern = /@(ts-ignore|ts-expect-error)\b|\bas\s+unknown\s+as\b
|
|
8
|
+
const tsEscapePattern = /@(ts-ignore|ts-expect-error|ts-nocheck)\b|\bas\s+unknown\s+as\b|<[^>\n]*\bany\b[^>\n]*>|(?:^|[^A-Za-z0-9_$])(?:as\s+any|:\s*any\b)/;
|
|
9
|
+
const crossLineUnknownCastPattern = /\bas\s+unknown\s+as\b/;
|
|
10
10
|
export function checkTypeBoundaries({ repoRoot = defaultRepoRoot, escapeAllowlistPath = path.join(repoRoot, "scripts/type-escape-allowlist.json"), } = {}) {
|
|
11
11
|
const files = collectSourceFiles(repoRoot);
|
|
12
12
|
const violations = [];
|
|
13
13
|
const escapeAllowlist = readEscapeAllowlist(escapeAllowlistPath);
|
|
14
|
+
const observedEscapes = new Set();
|
|
14
15
|
for (const file of files) {
|
|
15
16
|
const absolutePath = path.join(repoRoot, file);
|
|
16
17
|
const content = fs.readFileSync(absolutePath, "utf8");
|
|
@@ -25,10 +26,23 @@ export function checkTypeBoundaries({ repoRoot = defaultRepoRoot, escapeAllowlis
|
|
|
25
26
|
line: index + 1,
|
|
26
27
|
message: `${file}:${index + 1} uses a TypeScript escape hatch that requires review`,
|
|
27
28
|
};
|
|
29
|
+
observedEscapes.add(escapeKey(violation));
|
|
28
30
|
if (!isEscapeAllowed(escapeAllowlist, violation))
|
|
29
31
|
violations.push(violation);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
34
|
+
if (crossLineUnknownCastPattern.test(content) && !lines.some((line) => /\bas\s+unknown\s+as\b/.test(line))) {
|
|
35
|
+
const line = lineForOffset(content, content.search(crossLineUnknownCastPattern));
|
|
36
|
+
const violation = {
|
|
37
|
+
code: "ts-escape-hatch",
|
|
38
|
+
file,
|
|
39
|
+
line,
|
|
40
|
+
message: `${file}:${line} uses a cross-line TypeScript escape hatch that requires review`,
|
|
41
|
+
};
|
|
42
|
+
observedEscapes.add(escapeKey(violation));
|
|
43
|
+
if (!isEscapeAllowed(escapeAllowlist, violation))
|
|
44
|
+
violations.push(violation);
|
|
45
|
+
}
|
|
32
46
|
}
|
|
33
47
|
for (const imported of imports) {
|
|
34
48
|
if (!isLocalSpecifier(imported.specifier))
|
|
@@ -52,6 +66,16 @@ export function checkTypeBoundaries({ repoRoot = defaultRepoRoot, escapeAllowlis
|
|
|
52
66
|
}
|
|
53
67
|
}
|
|
54
68
|
}
|
|
69
|
+
for (const allowed of escapeAllowlist.values()) {
|
|
70
|
+
if (observedEscapes.has(allowed.key))
|
|
71
|
+
continue;
|
|
72
|
+
violations.push({
|
|
73
|
+
code: "stale-ts-escape-allowlist",
|
|
74
|
+
file: allowed.file,
|
|
75
|
+
line: allowed.line,
|
|
76
|
+
message: `${allowed.file}:${allowed.line} is listed in ${path.relative(repoRoot, escapeAllowlistPath)} but no matching TypeScript escape hatch was found`,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
55
79
|
return { ok: violations.length === 0, violations };
|
|
56
80
|
}
|
|
57
81
|
function collectSourceFiles(repoRoot) {
|
|
@@ -86,7 +110,7 @@ function parseImports(content) {
|
|
|
86
110
|
imports.push({
|
|
87
111
|
kind: match[1] || "import",
|
|
88
112
|
typeOnly: match[2] === "type ",
|
|
89
|
-
specifier: match[3] || match[4],
|
|
113
|
+
specifier: match[3] || match[4] || "",
|
|
90
114
|
});
|
|
91
115
|
}
|
|
92
116
|
return imports;
|
|
@@ -134,17 +158,35 @@ function isTypeOnlyTypeScriptImport(file, imported) {
|
|
|
134
158
|
}
|
|
135
159
|
function readEscapeAllowlist(allowlistPath) {
|
|
136
160
|
if (!allowlistPath || !fs.existsSync(allowlistPath))
|
|
137
|
-
return new
|
|
161
|
+
return new Map();
|
|
138
162
|
const parsed = JSON.parse(fs.readFileSync(allowlistPath, "utf8"));
|
|
139
163
|
const entries = Array.isArray(parsed) ? parsed : parsed.escapes || [];
|
|
140
|
-
return new
|
|
141
|
-
if (typeof entry === "string")
|
|
142
|
-
return
|
|
143
|
-
|
|
164
|
+
return new Map(entries.map((entry) => {
|
|
165
|
+
if (typeof entry === "string") {
|
|
166
|
+
return [
|
|
167
|
+
entry,
|
|
168
|
+
{
|
|
169
|
+
key: entry,
|
|
170
|
+
file: entry.split(":")[0] || entry,
|
|
171
|
+
line: Number.parseInt(entry.split(":")[1] || "", 10) || undefined,
|
|
172
|
+
code: entry.split(":")[2] || "ts-escape-hatch",
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
const key = `${entry.file}:${entry.line}:${entry.code || "ts-escape-hatch"}`;
|
|
177
|
+
return [key, { key, file: entry.file, line: entry.line, code: entry.code || "ts-escape-hatch" }];
|
|
144
178
|
}));
|
|
145
179
|
}
|
|
146
180
|
function isEscapeAllowed(allowlist, violation) {
|
|
147
|
-
return allowlist.has(
|
|
181
|
+
return allowlist.has(escapeKey(violation)) || allowlist.has(`${violation.file}:${violation.line}`);
|
|
182
|
+
}
|
|
183
|
+
function escapeKey(violation) {
|
|
184
|
+
return `${violation.file}:${violation.line}:${violation.code}`;
|
|
185
|
+
}
|
|
186
|
+
function lineForOffset(content, offset) {
|
|
187
|
+
if (offset < 0)
|
|
188
|
+
return 1;
|
|
189
|
+
return content.slice(0, offset).split(/\r?\n/).length;
|
|
148
190
|
}
|
|
149
191
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
150
192
|
const result = checkTypeBoundaries();
|
|
@@ -1,9 +1,31 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
// Dashboard command parsing stays behavior-first until command handler types are modeled.
|
|
3
2
|
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { normalizeTarget, serveDashboardWorkbench, writeDashboardFolder, writeDashboardSingleFile, } from "../lib/harness-core.mjs";
|
|
6
6
|
import { dashboardWatchRoots } from "../lib/harness-paths.mjs";
|
|
7
|
+
export async function runDevDashboardCommand({ takeFlag, takeOption, targetArg }) {
|
|
8
|
+
const open = !takeFlag("--no-open");
|
|
9
|
+
const outDir = takeOption("--out-dir", "");
|
|
10
|
+
const host = takeOption("--host", "127.0.0.1");
|
|
11
|
+
const port = Number(takeOption("--port", "0"));
|
|
12
|
+
const localeOverride = takeOption("--locale", "");
|
|
13
|
+
const target = targetArg();
|
|
14
|
+
const usesDefaultOutDir = !outDir;
|
|
15
|
+
const dashboardOutDir = outDir || defaultDevOutDir(target);
|
|
16
|
+
const opts = {
|
|
17
|
+
...(localeOverride ? { localeOverride } : {}),
|
|
18
|
+
recoverGeneratedDashboard: usesDefaultOutDir,
|
|
19
|
+
replaceExistingDashboardOutput: usesDefaultOutDir,
|
|
20
|
+
};
|
|
21
|
+
try {
|
|
22
|
+
await serveDashboardWorkbench(dashboardOutDir, target, { ...opts, host, port, autoRefresh: true, open, label: "harness dev" });
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
console.error(errorMessage(error));
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
7
29
|
export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
|
|
8
30
|
const watch = takeFlag("--watch");
|
|
9
31
|
const workbench = takeFlag("--workbench");
|
|
@@ -19,11 +41,11 @@ export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
|
|
|
19
41
|
process.exit(2);
|
|
20
42
|
}
|
|
21
43
|
try {
|
|
22
|
-
|
|
23
|
-
await serveDashboardWorkbench(outDir, targetArg(), { ...opts, host, port });
|
|
44
|
+
requireV2DashboardTarget(targetArg());
|
|
45
|
+
await serveDashboardWorkbench(outDir, targetArg(), { ...opts, host, port: Number(port) });
|
|
24
46
|
}
|
|
25
47
|
catch (error) {
|
|
26
|
-
console.error(error
|
|
48
|
+
console.error(errorMessage(error));
|
|
27
49
|
process.exit(1);
|
|
28
50
|
}
|
|
29
51
|
}
|
|
@@ -33,28 +55,29 @@ export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
|
|
|
33
55
|
process.exit(2);
|
|
34
56
|
}
|
|
35
57
|
const target = targetArg();
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const watchRoots = dashboardWatchRoots(normalizedTarget.harness);
|
|
58
|
+
const harnessPaths = requireV2DashboardTarget(target);
|
|
59
|
+
const watchRoots = dashboardWatchRoots(harnessPaths);
|
|
39
60
|
const regenerate = () => {
|
|
40
61
|
try {
|
|
41
62
|
console.log(writeDashboardFolder(outDir, target, opts));
|
|
42
63
|
console.log(`dashboard regenerated: ${new Date().toISOString()}`);
|
|
43
64
|
}
|
|
44
65
|
catch (error) {
|
|
45
|
-
console.error(`dashboard regeneration failed: ${error
|
|
66
|
+
console.error(`dashboard regeneration failed: ${errorMessage(error)}`);
|
|
46
67
|
}
|
|
47
68
|
};
|
|
48
69
|
regenerate();
|
|
49
70
|
let timer = null;
|
|
50
71
|
const watchers = watchRoots.map((watchRoot) => fs.watch(watchRoot, { recursive: true }, () => {
|
|
51
|
-
|
|
72
|
+
if (timer)
|
|
73
|
+
clearTimeout(timer);
|
|
52
74
|
timer = setTimeout(regenerate, 300);
|
|
53
75
|
}));
|
|
54
76
|
const close = () => {
|
|
55
77
|
for (const watcher of watchers)
|
|
56
78
|
watcher.close();
|
|
57
|
-
|
|
79
|
+
if (timer)
|
|
80
|
+
clearTimeout(timer);
|
|
58
81
|
process.exit(0);
|
|
59
82
|
};
|
|
60
83
|
process.on("SIGINT", close);
|
|
@@ -62,7 +85,7 @@ export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
|
|
|
62
85
|
console.log(`watching ${watchRoots.join(", ")}`);
|
|
63
86
|
await new Promise(() => { });
|
|
64
87
|
}
|
|
65
|
-
|
|
88
|
+
requireV2DashboardTarget(targetArg());
|
|
66
89
|
if (outDir) {
|
|
67
90
|
console.log(writeDashboardFolder(outDir, targetArg(), opts));
|
|
68
91
|
}
|
|
@@ -71,10 +94,25 @@ export async function runDashboardCommand({ takeFlag, takeOption, targetArg }) {
|
|
|
71
94
|
}
|
|
72
95
|
process.exit(0);
|
|
73
96
|
}
|
|
74
|
-
function
|
|
97
|
+
function requireV2DashboardTarget(target) {
|
|
75
98
|
const normalizedTarget = normalizeTarget(target);
|
|
76
|
-
|
|
77
|
-
|
|
99
|
+
const harnessPaths = readV2HarnessPaths(normalizedTarget);
|
|
100
|
+
if (harnessPaths)
|
|
101
|
+
return harnessPaths;
|
|
78
102
|
console.error("dashboard requires v2 harness structure; run `harness migrate-structure --plan` then `harness migrate-structure --apply`");
|
|
79
103
|
process.exit(1);
|
|
80
104
|
}
|
|
105
|
+
function defaultDevOutDir(targetInput) {
|
|
106
|
+
const target = path.resolve(targetInput || ".");
|
|
107
|
+
return path.join(os.tmpdir(), "coding-agent-harness-dev", `${path.basename(target) || "project"}-${Buffer.from(target).toString("hex").slice(0, 16)}`);
|
|
108
|
+
}
|
|
109
|
+
function readV2HarnessPaths(target) {
|
|
110
|
+
const harness = target.harness;
|
|
111
|
+
return isRecord(harness) && harness.version === 2 ? harness : null;
|
|
112
|
+
}
|
|
113
|
+
function isRecord(value) {
|
|
114
|
+
return typeof value === "object" && value !== null;
|
|
115
|
+
}
|
|
116
|
+
function errorMessage(error) {
|
|
117
|
+
return error instanceof Error ? error.message : String(error);
|
|
118
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
1
|
import { applyStructureMigration, buildMigrationPlan, planStructureMigration, runMigration, verifyMigrationSession, } from "../lib/harness-core.mjs";
|
|
3
2
|
import { applyTaskAuditIndexMigration, planTaskAuditIndexMigration, } from "../lib/task-audit-migration.mjs";
|
|
4
3
|
export function runMigrationCommand(command, { args, takeFlag, takeOption, targetArg }) {
|
|
@@ -8,6 +7,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
|
|
|
8
7
|
const planOnly = takeFlag("--plan");
|
|
9
8
|
const force = takeFlag("--force");
|
|
10
9
|
try {
|
|
10
|
+
const shouldApply = apply && !planOnly;
|
|
11
11
|
const result = apply && !planOnly
|
|
12
12
|
? applyStructureMigration(targetArg(), { force })
|
|
13
13
|
: planStructureMigration(targetArg());
|
|
@@ -15,7 +15,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
|
|
|
15
15
|
console.log(JSON.stringify(result, null, 2));
|
|
16
16
|
}
|
|
17
17
|
else {
|
|
18
|
-
console.log(`Structure migration ${
|
|
18
|
+
console.log(`Structure migration ${shouldApply ? "applied" : "plan"}: ${result.target}`);
|
|
19
19
|
console.log(`manifest: ${result.manifest}`);
|
|
20
20
|
console.log(`actions: ${result.summary.actions}`);
|
|
21
21
|
for (const action of result.actions || [])
|
|
@@ -23,7 +23,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
catch (error) {
|
|
26
|
-
console.error(error
|
|
26
|
+
console.error(errorMessage(error));
|
|
27
27
|
process.exit(1);
|
|
28
28
|
}
|
|
29
29
|
return;
|
|
@@ -52,10 +52,11 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
|
|
|
52
52
|
process.exit(result.failures?.length ? 1 : 0);
|
|
53
53
|
}
|
|
54
54
|
catch (error) {
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
const plan = readProperty(error, "plan");
|
|
56
|
+
if (json && plan)
|
|
57
|
+
console.error(JSON.stringify(plan, null, 2));
|
|
57
58
|
else
|
|
58
|
-
console.error(error
|
|
59
|
+
console.error(errorMessage(error));
|
|
59
60
|
process.exit(1);
|
|
60
61
|
}
|
|
61
62
|
}
|
|
@@ -99,7 +100,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
|
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
102
|
catch (error) {
|
|
102
|
-
console.error(error
|
|
103
|
+
console.error(errorMessage(error));
|
|
103
104
|
process.exit(1);
|
|
104
105
|
}
|
|
105
106
|
return;
|
|
@@ -122,7 +123,7 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
|
|
|
122
123
|
}), null, 2));
|
|
123
124
|
}
|
|
124
125
|
catch (error) {
|
|
125
|
-
console.error(error
|
|
126
|
+
console.error(errorMessage(error));
|
|
126
127
|
process.exit(1);
|
|
127
128
|
}
|
|
128
129
|
return;
|
|
@@ -150,3 +151,12 @@ export function runMigrationCommand(command, { args, takeFlag, takeOption, targe
|
|
|
150
151
|
}
|
|
151
152
|
throw new Error(`Unsupported migration command: ${command}`);
|
|
152
153
|
}
|
|
154
|
+
function readProperty(value, key) {
|
|
155
|
+
return isRecord(value) ? value[key] : undefined;
|
|
156
|
+
}
|
|
157
|
+
function isRecord(value) {
|
|
158
|
+
return typeof value === "object" && value !== null;
|
|
159
|
+
}
|
|
160
|
+
function errorMessage(error) {
|
|
161
|
+
return error instanceof Error ? error.message : String(error);
|
|
162
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { normalizeTarget, prepareModuleRegistration, prepareModuleScaffold, prepareModuleUnregister, readHarnessModules, } from "../lib/harness-core.mjs";
|
|
2
|
+
import { takeRepeatedOptionsFromArgs } from "../lib/command-registry.mjs";
|
|
3
|
+
import { beginGovernanceSync, commitGovernanceSync, governanceRelativePaths, releaseGovernanceSync } from "../lib/governance-sync.mjs";
|
|
4
|
+
export function runModuleCommand({ args, takeFlag, takeOption, targetArg }) {
|
|
5
|
+
const subcommand = args.shift() || "list";
|
|
6
|
+
const json = takeFlag("--json");
|
|
7
|
+
if (subcommand === "list") {
|
|
8
|
+
const modules = readHarnessModules(targetArg());
|
|
9
|
+
const items = Object.entries(modules.items || {}).map(([key, module]) => ({ key, ...module }));
|
|
10
|
+
if (json)
|
|
11
|
+
console.log(JSON.stringify({ schema: modules.schema, generatedView: modules.generatedView, modules: items }, null, 2));
|
|
12
|
+
else
|
|
13
|
+
for (const item of items)
|
|
14
|
+
console.log(`${item.key}\t${item.status || "planned"}\t${item.title || item.key}`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (subcommand === "inspect") {
|
|
18
|
+
const moduleKey = args.shift();
|
|
19
|
+
if (!moduleKey) {
|
|
20
|
+
console.error("Missing module key");
|
|
21
|
+
process.exit(2);
|
|
22
|
+
}
|
|
23
|
+
const modules = readHarnessModules(targetArg());
|
|
24
|
+
const module = modules.items[moduleKey];
|
|
25
|
+
if (!module) {
|
|
26
|
+
console.error(`Module is not registered: ${moduleKey}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
console.log(JSON.stringify({ key: moduleKey, ...module }, null, 2));
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (subcommand === "register") {
|
|
33
|
+
const dryRun = takeFlag("--dry-run");
|
|
34
|
+
const moduleKey = args.shift();
|
|
35
|
+
if (!moduleKey) {
|
|
36
|
+
console.error("Missing module key");
|
|
37
|
+
process.exit(2);
|
|
38
|
+
}
|
|
39
|
+
const input = {
|
|
40
|
+
title: takeOption("--title", ""),
|
|
41
|
+
prefix: takeOption("--prefix", ""),
|
|
42
|
+
status: takeOption("--status", "planned"),
|
|
43
|
+
branch: takeOption("--branch", ""),
|
|
44
|
+
owner: takeOption("--owner", "coordinator"),
|
|
45
|
+
currentStep: takeOption("--current-step", ""),
|
|
46
|
+
locale: takeOption("--locale", ""),
|
|
47
|
+
scope: takeRepeatedOptionsFromArgs(args, "--scope"),
|
|
48
|
+
shared: takeRepeatedOptionsFromArgs(args, "--shared"),
|
|
49
|
+
dependsOn: takeRepeatedOptionsFromArgs(args, "--depends-on"),
|
|
50
|
+
};
|
|
51
|
+
const target = normalizeTarget(targetArg());
|
|
52
|
+
const planned = prepareModuleRegistration(target, moduleKey, input, { dryRun: true });
|
|
53
|
+
const context = beginGovernanceSync(target, {
|
|
54
|
+
operation: `module register ${moduleKey}`,
|
|
55
|
+
dryRun,
|
|
56
|
+
allowDirtyWorktree: true,
|
|
57
|
+
allowedRelativePaths: governanceRelativePaths(planned.changes),
|
|
58
|
+
});
|
|
59
|
+
try {
|
|
60
|
+
const result = prepareModuleRegistration(target, moduleKey, input, { dryRun });
|
|
61
|
+
const commit = commitGovernanceSync(context, governanceRelativePaths(result.changes), { message: `chore(harness): register module ${result.moduleKey}` });
|
|
62
|
+
console.log(JSON.stringify({ ...result, governance: { commit } }, null, 2));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.error(errorMessage(error));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
releaseGovernanceSync(context);
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (subcommand === "unregister") {
|
|
74
|
+
const dryRun = takeFlag("--dry-run");
|
|
75
|
+
const moduleKey = args.shift();
|
|
76
|
+
if (!moduleKey) {
|
|
77
|
+
console.error("Missing module key");
|
|
78
|
+
process.exit(2);
|
|
79
|
+
}
|
|
80
|
+
const target = normalizeTarget(targetArg());
|
|
81
|
+
const planned = prepareModuleUnregister(target, moduleKey, { dryRun: true });
|
|
82
|
+
const context = beginGovernanceSync(target, {
|
|
83
|
+
operation: `module unregister ${moduleKey}`,
|
|
84
|
+
dryRun,
|
|
85
|
+
allowDirtyWorktree: true,
|
|
86
|
+
allowedRelativePaths: governanceRelativePaths(planned.changes),
|
|
87
|
+
});
|
|
88
|
+
try {
|
|
89
|
+
const result = prepareModuleUnregister(target, moduleKey, { dryRun });
|
|
90
|
+
const commit = commitGovernanceSync(context, governanceRelativePaths(result.changes), { message: `chore(harness): unregister module ${result.moduleKey}` });
|
|
91
|
+
console.log(JSON.stringify({ ...result, governance: { commit } }, null, 2));
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
console.error(errorMessage(error));
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
releaseGovernanceSync(context);
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (subcommand === "scaffold") {
|
|
103
|
+
const dryRun = takeFlag("--dry-run");
|
|
104
|
+
const all = takeFlag("--all");
|
|
105
|
+
const locale = takeOption("--locale", "");
|
|
106
|
+
const moduleKey = all ? "" : args.shift();
|
|
107
|
+
if (!all && !moduleKey) {
|
|
108
|
+
console.error("Missing module key");
|
|
109
|
+
process.exit(2);
|
|
110
|
+
}
|
|
111
|
+
const target = normalizeTarget(targetArg());
|
|
112
|
+
const modules = readHarnessModules(target);
|
|
113
|
+
const keys = all ? Object.keys(modules.items || {}).sort() : [moduleKey || ""];
|
|
114
|
+
const plannedChanges = keys.flatMap((key) => prepareModuleScaffold(target, key, { dryRun: true, locale }).changes);
|
|
115
|
+
const context = beginGovernanceSync(target, {
|
|
116
|
+
operation: all ? "module scaffold --all" : `module scaffold ${moduleKey}`,
|
|
117
|
+
dryRun,
|
|
118
|
+
allowDirtyWorktree: true,
|
|
119
|
+
allowedRelativePaths: governanceRelativePaths(plannedChanges),
|
|
120
|
+
allowDirtyWriteScope: true,
|
|
121
|
+
});
|
|
122
|
+
try {
|
|
123
|
+
const results = keys.map((key) => prepareModuleScaffold(target, key, { dryRun, locale }));
|
|
124
|
+
const changes = results.flatMap((result) => result.changes);
|
|
125
|
+
const commit = commitGovernanceSync(context, governanceRelativePaths(changes), { message: all ? "chore(harness): scaffold registered modules" : `chore(harness): scaffold module ${moduleKey}` });
|
|
126
|
+
console.log(JSON.stringify({ modules: results, changes, governance: { commit } }, null, 2));
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error(errorMessage(error));
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
releaseGovernanceSync(context);
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
console.error(`Unknown module subcommand: ${subcommand}`);
|
|
138
|
+
process.exit(2);
|
|
139
|
+
}
|
|
140
|
+
function errorMessage(error) {
|
|
141
|
+
return error instanceof Error ? error.message : String(error);
|
|
142
|
+
}
|