coding-agent-harness 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +2 -2
- package/README.md +63 -3
- package/README.zh-CN.md +52 -3
- package/SKILL.md +43 -43
- package/dist/build-dist.mjs +189 -0
- package/dist/check-dist-observation.mjs +428 -0
- package/dist/check-harness.mjs +489 -0
- package/dist/check-import-graph.mjs +511 -0
- package/dist/check-runtime-emit.mjs +304 -0
- package/dist/check-type-boundaries.mjs +139 -0
- package/dist/commands/dashboard-command.mjs +80 -0
- package/dist/commands/migration-command.mjs +152 -0
- package/dist/commands/preset-command.mjs +91 -0
- package/dist/commands/task-command.mjs +324 -0
- package/dist/harness.mjs +304 -0
- package/dist/lib/capability-registry.mjs +643 -0
- package/dist/lib/check-module-parallel.mjs +227 -0
- package/dist/lib/check-profiles.mjs +414 -0
- package/dist/lib/check-task-contracts.mjs +54 -0
- package/dist/lib/core-shared.mjs +254 -0
- package/dist/lib/dashboard-data.mjs +608 -0
- package/dist/lib/dashboard-workbench.mjs +334 -0
- package/dist/lib/dashboard-writer.mjs +200 -0
- package/dist/lib/git-status-summary.mjs +45 -0
- package/dist/lib/governance-index-generator.mjs +236 -0
- package/dist/lib/governance-sync.mjs +617 -0
- package/dist/lib/governance-table-boundary.mjs +161 -0
- package/{scripts → dist}/lib/harness-core.mjs +2 -0
- package/dist/lib/harness-paths.mjs +338 -0
- package/dist/lib/lesson-maintenance.mjs +139 -0
- package/dist/lib/markdown-utils.mjs +193 -0
- package/dist/lib/migration-planner.mjs +439 -0
- package/dist/lib/migration-support.mjs +317 -0
- package/dist/lib/phase-kind.mjs +46 -0
- package/dist/lib/preset-audit-contracts.mjs +40 -0
- package/dist/lib/preset-engine.mjs +516 -0
- package/dist/lib/preset-registry.mjs +831 -0
- package/dist/lib/preset-resource-contracts.mjs +83 -0
- package/dist/lib/review-confirm-git-gate.mjs +244 -0
- package/dist/lib/status-builder.mjs +87 -0
- package/{scripts → dist}/lib/status-dashboard-renderer.mjs +44 -46
- package/dist/lib/structure-migration.mjs +404 -0
- package/dist/lib/subagent-authorization-audit.mjs +198 -0
- package/dist/lib/task-audit-metadata.mjs +376 -0
- package/dist/lib/task-audit-migration.mjs +355 -0
- package/dist/lib/task-completion-consistency.mjs +29 -0
- package/dist/lib/task-index.mjs +133 -0
- package/dist/lib/task-lesson-candidates.mjs +239 -0
- package/dist/lib/task-lesson-sedimentation.mjs +300 -0
- package/dist/lib/task-lifecycle/create-task-helpers.mjs +84 -0
- package/dist/lib/task-lifecycle/phase-sync.mjs +82 -0
- package/dist/lib/task-lifecycle/review-confirm.mjs +93 -0
- package/dist/lib/task-lifecycle/review-gates.mjs +62 -0
- package/dist/lib/task-lifecycle/review-submission.mjs +52 -0
- package/dist/lib/task-lifecycle/scaffold-provenance.mjs +54 -0
- package/dist/lib/task-lifecycle/template-files.mjs +52 -0
- package/dist/lib/task-lifecycle/text-utils.mjs +26 -0
- package/dist/lib/task-lifecycle.mjs +611 -0
- package/dist/lib/task-metadata.mjs +116 -0
- package/dist/lib/task-review-model.mjs +474 -0
- package/dist/lib/task-scanner.mjs +439 -0
- package/dist/lib/task-tombstone-commands.mjs +125 -0
- package/dist/postinstall.mjs +14 -0
- package/dist/run-built-tests.mjs +84 -0
- package/docs-release/README.md +1 -0
- package/docs-release/architecture/overview.md +12 -12
- package/docs-release/architecture/overview.zh-CN.md +12 -12
- package/docs-release/architecture/system-explainer/01-system-overview.md +15 -14
- package/docs-release/architecture/system-explainer/02-module-dependency.md +8 -8
- package/docs-release/architecture/system-explainer/03-task-lifecycle.md +3 -3
- package/docs-release/architecture/system-explainer/04-check-and-governance.md +9 -7
- package/docs-release/architecture/system-explainer/05-data-flow.md +5 -5
- package/docs-release/architecture/system-explainer/06-preset-and-migration.md +1 -4
- package/docs-release/architecture/system-explainer/en-US/01-system-overview.md +15 -14
- package/docs-release/architecture/system-explainer/en-US/02-module-dependency.md +8 -8
- package/docs-release/architecture/system-explainer/en-US/03-task-lifecycle.md +3 -3
- package/docs-release/architecture/system-explainer/en-US/04-check-and-governance.md +10 -8
- 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 +1 -4
- package/docs-release/guides/agent-installation.en-US.md +14 -8
- package/docs-release/guides/agent-installation.md +14 -8
- package/docs-release/guides/contributing.md +3 -3
- package/docs-release/guides/contributing.zh-CN.md +3 -3
- package/docs-release/guides/document-audience-and-surfaces.en-US.md +10 -10
- package/docs-release/guides/document-audience-and-surfaces.md +10 -10
- package/docs-release/guides/legacy-migration-agent-prompt.md +25 -2
- package/docs-release/guides/legacy-migration-agent-prompt.zh-CN.md +25 -2
- package/docs-release/guides/migration-playbook.en-US.md +63 -1
- package/docs-release/guides/migration-playbook.md +59 -1
- package/docs-release/guides/parent-control-repository-pattern.en-US.md +25 -25
- package/docs-release/guides/parent-control-repository-pattern.md +25 -25
- package/docs-release/guides/preset-development.md +2 -2
- package/docs-release/guides/repository-operating-models.en-US.md +21 -21
- package/docs-release/guides/repository-operating-models.md +21 -21
- package/docs-release/guides/task-state-machine.en-US.md +5 -5
- package/docs-release/guides/task-state-machine.md +5 -5
- package/docs-release/guides/typescript-runtime-migration-closeout.md +96 -0
- package/examples/minimal-project/AGENTS.md +2 -2
- package/examples/minimal-project/coding-agent-harness/harness.yaml +14 -0
- package/examples/minimal-project/coding-agent-harness/planning/tasks/demo-task/progress.md +11 -0
- package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/review.md +1 -1
- package/package.json +20 -12
- 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/standard-task/preset.yaml +2 -2
- package/references/adversarial-review-standard.md +2 -2
- package/references/agents-md-pattern.md +14 -14
- package/references/cadence-ledger.md +1 -1
- package/references/ci-cd-standard.md +1 -1
- package/references/delivery-operating-model-standard.md +4 -4
- package/references/docs-directory-standard.md +65 -159
- package/references/external-source-intake-standard.md +10 -10
- package/references/harness-ledger.md +5 -5
- package/references/legacy-12-phase-bootstrap.md +2 -2
- package/references/lessons-governance.md +15 -15
- package/references/long-running-task-standard.md +6 -6
- package/references/module-parallel-standard.md +34 -34
- package/references/planning-loop.md +6 -6
- package/references/project-onboarding-audit.md +4 -4
- package/references/regression-system.md +2 -2
- package/references/repo-governance-standard.md +4 -4
- package/references/review-routing-standard.md +1 -1
- package/references/ssot-governance.md +19 -19
- package/references/taskr-gap-analysis.md +5 -5
- package/references/walkthrough-closeout.md +14 -14
- package/references/worktree-parallel.md +3 -3
- package/skills/preset-creator/references/complex-task-skeleton/task_plan.md +1 -1
- package/skills/preset-creator/references/preset-package-skeleton.md +5 -5
- package/templates/AGENTS.md.template +26 -26
- package/templates/architecture/README.md +4 -4
- package/templates/architecture/service-catalog.md +2 -2
- package/templates/architecture/services/service-template.md +1 -1
- package/templates/dashboard/assets/app-src/20-overview.js +11 -5
- package/templates/dashboard/assets/app-src/40-modules.js +1 -1
- package/templates/dashboard/assets/app.js +12 -6
- package/templates/dashboard/assets/i18n.js +4 -2
- package/templates/development/README.md +10 -10
- package/templates/development/cross-repo-debugging.md +3 -3
- package/templates/development/external-context/service-template.md +2 -2
- package/templates/development/external-source-packs/README.md +4 -4
- package/templates/integrations/README.md +4 -4
- package/templates/integrations/api-contract.md +2 -2
- package/templates/integrations/event-contract.md +2 -2
- package/templates/integrations/third-party/vendor-template.md +2 -2
- package/templates/integrations/webhook-contract.md +2 -2
- package/templates/ledger/Harness-Ledger.md +1 -1
- package/templates/planning/INDEX.md +1 -0
- package/templates/planning/module_session_prompt.md +1 -1
- package/templates/planning/task_plan.md +1 -1
- package/templates/planning/walkthrough.md +47 -0
- package/templates/reference/docs-library-standard.md +8 -8
- package/templates/reference/external-source-intake-standard.md +15 -15
- package/templates/reference/repo-governance-standard.md +1 -1
- package/templates/ssot/Module-Registry.md +1 -1
- package/templates/walkthrough/walkthrough-template.md +2 -2
- package/templates-zh-CN/AGENTS.md.template +26 -26
- package/templates-zh-CN/CLAUDE.md.template +1 -1
- package/templates-zh-CN/architecture/README.md +4 -4
- 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 +10 -10
- package/templates-zh-CN/development/cross-repo-debugging.md +3 -3
- package/templates-zh-CN/development/external-context/service-template.md +2 -2
- package/templates-zh-CN/development/external-source-packs/README.md +4 -4
- package/templates-zh-CN/integrations/README.md +4 -4
- package/templates-zh-CN/integrations/api-contract.md +2 -2
- package/templates-zh-CN/integrations/event-contract.md +2 -2
- package/templates-zh-CN/integrations/third-party/vendor-template.md +2 -2
- package/templates-zh-CN/integrations/webhook-contract.md +2 -2
- 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/planning/module_session_prompt.md +11 -11
- package/templates-zh-CN/planning/walkthrough.md +47 -0
- package/templates-zh-CN/reference/adversarial-review-standard.md +2 -2
- package/templates-zh-CN/reference/delivery-operating-model-standard.md +3 -3
- package/templates-zh-CN/reference/docs-library-standard.md +28 -28
- package/templates-zh-CN/reference/execution-workflow-standard.md +1 -1
- package/templates-zh-CN/reference/external-source-intake-standard.md +16 -16
- package/templates-zh-CN/reference/harness-ledger-standard.md +6 -6
- 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 +1 -1
- package/templates-zh-CN/reference/walkthrough-standard.md +7 -7
- 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 +3 -3
- package/templates-zh-CN/ssot/Module-Registry.md +3 -3
- package/templates-zh-CN/ssot/Regression-SSoT.md +2 -2
- package/templates-zh-CN/walkthrough/walkthrough-template.md +5 -5
- package/tsconfig.dist.json +16 -0
- package/tsconfig.json +25 -0
- package/tsconfig.runtime.json +24 -0
- package/examples/minimal-project/.harness-capabilities.json +0 -8
- package/examples/minimal-project/docs/09-PLANNING/TASKS/demo-task/progress.md +0 -11
- package/scripts/check-harness.mjs +0 -508
- package/scripts/commands/dashboard-command.mjs +0 -67
- package/scripts/commands/migration-command.mjs +0 -126
- package/scripts/commands/preset-command.mjs +0 -73
- package/scripts/commands/task-command.mjs +0 -328
- package/scripts/harness.mjs +0 -291
- package/scripts/lib/capability-registry.mjs +0 -587
- package/scripts/lib/check-module-parallel.mjs +0 -230
- package/scripts/lib/check-profiles.mjs +0 -372
- package/scripts/lib/check-task-contracts.mjs +0 -55
- package/scripts/lib/core-shared.mjs +0 -249
- package/scripts/lib/dashboard-data.mjs +0 -520
- package/scripts/lib/dashboard-workbench.mjs +0 -336
- package/scripts/lib/dashboard-writer.mjs +0 -202
- package/scripts/lib/git-status-summary.mjs +0 -46
- package/scripts/lib/governance-index-generator.mjs +0 -174
- package/scripts/lib/governance-sync.mjs +0 -611
- package/scripts/lib/governance-table-boundary.mjs +0 -175
- package/scripts/lib/lesson-maintenance.mjs +0 -152
- package/scripts/lib/markdown-utils.mjs +0 -191
- package/scripts/lib/migration-planner.mjs +0 -476
- package/scripts/lib/migration-support.mjs +0 -312
- package/scripts/lib/phase-kind.mjs +0 -50
- package/scripts/lib/preset-audit-contracts.mjs +0 -37
- package/scripts/lib/preset-engine.mjs +0 -494
- package/scripts/lib/preset-registry.mjs +0 -776
- package/scripts/lib/preset-resource-contracts.mjs +0 -83
- package/scripts/lib/review-confirm-git-gate.mjs +0 -248
- package/scripts/lib/status-builder.mjs +0 -88
- package/scripts/lib/subagent-authorization-audit.mjs +0 -196
- package/scripts/lib/task-audit-metadata.mjs +0 -385
- package/scripts/lib/task-audit-migration.mjs +0 -350
- package/scripts/lib/task-completion-consistency.mjs +0 -26
- package/scripts/lib/task-index.mjs +0 -93
- package/scripts/lib/task-lesson-candidates.mjs +0 -242
- package/scripts/lib/task-lesson-sedimentation.mjs +0 -326
- package/scripts/lib/task-lifecycle/create-task-helpers.mjs +0 -67
- package/scripts/lib/task-lifecycle/phase-sync.mjs +0 -88
- package/scripts/lib/task-lifecycle/review-confirm.mjs +0 -112
- package/scripts/lib/task-lifecycle/review-gates.mjs +0 -73
- package/scripts/lib/task-lifecycle/review-submission.mjs +0 -63
- package/scripts/lib/task-lifecycle/scaffold-provenance.mjs +0 -49
- package/scripts/lib/task-lifecycle/template-files.mjs +0 -53
- package/scripts/lib/task-lifecycle/text-utils.mjs +0 -24
- package/scripts/lib/task-lifecycle.mjs +0 -616
- package/scripts/lib/task-metadata.mjs +0 -118
- package/scripts/lib/task-review-model.mjs +0 -455
- package/scripts/lib/task-scanner.mjs +0 -503
- package/scripts/lib/task-tombstone-commands.mjs +0 -140
- package/scripts/postinstall.mjs +0 -14
- package/templates/walkthrough/Closeout-SSoT.md +0 -43
- package/templates-zh-CN/walkthrough/Closeout-SSoT.md +0 -42
- /package/examples/minimal-project/{docs → coding-agent-harness/governance/generated}/Harness-Ledger.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/INDEX.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/brief.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/execution_strategy.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/findings.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/lesson_candidates.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/task_plan.md +0 -0
- /package/examples/minimal-project/{docs/09-PLANNING/TASKS → coding-agent-harness/planning/tasks}/demo-task/visual_map.md +0 -0
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { readFileSafe, toPosix } from "./core-shared.mjs";
|
|
4
|
-
|
|
5
|
-
export function validatePresetResourcesForTask(target, task, presetPackage) {
|
|
6
|
-
const failures = [];
|
|
7
|
-
const taskRelativePath = String(task.path || "").replace(/^TARGET:/, "").replace(/^\/+/, "");
|
|
8
|
-
if (!taskRelativePath) return failures;
|
|
9
|
-
const taskDir = path.join(target.projectRoot, taskRelativePath);
|
|
10
|
-
const referenceIndex = readFileSafe(path.join(taskDir, "references/INDEX.md"));
|
|
11
|
-
const referenceRows = parseMarkdownRows(referenceIndex, ["ID", "Path"]);
|
|
12
|
-
const artifactIndex = readFileSafe(path.join(taskDir, "artifacts/INDEX.md"));
|
|
13
|
-
const artifactRows = parseMarkdownRows(artifactIndex, ["ID", "Path"]);
|
|
14
|
-
const taskPlan = task.taskPlanPath ? readFileSafe(path.join(target.projectRoot, String(task.taskPlanPath).replace(/^TARGET:/, "").replace(/^\/+/, ""))) : "";
|
|
15
|
-
const requiredReadRows = parseMarkdownRows(taskPlan, ["Reference", "Path"]);
|
|
16
|
-
const expectedReferencePaths = new Map();
|
|
17
|
-
for (const resource of Object.values(presetPackage.resources?.references || {})) {
|
|
18
|
-
const relativePath = toPosix(path.join(taskRelativePath, resource.path));
|
|
19
|
-
expectedReferencePaths.set(resource.index.id, `TARGET:${relativePath}`);
|
|
20
|
-
if (!fs.existsSync(path.join(target.projectRoot, relativePath))) {
|
|
21
|
-
failures.push(`${task.path} ${task.taskPreset} preset resource missing: TARGET:${relativePath}`);
|
|
22
|
-
}
|
|
23
|
-
if (!hasIndexedResource(referenceRows, resource.index.id, `TARGET:${relativePath}`)) {
|
|
24
|
-
failures.push(`${task.path} ${task.taskPreset} preset reference index missing ${resource.index.id}`);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
for (const resource of Object.values(presetPackage.resources?.artifacts || {})) {
|
|
28
|
-
const relativePath = toPosix(path.join(taskRelativePath, resource.path));
|
|
29
|
-
if (!fs.existsSync(path.join(target.projectRoot, relativePath))) {
|
|
30
|
-
failures.push(`${task.path} ${task.taskPreset} preset resource missing: TARGET:${relativePath}`);
|
|
31
|
-
}
|
|
32
|
-
if (!hasIndexedResource(artifactRows, resource.index.id, `TARGET:${relativePath}`)) {
|
|
33
|
-
failures.push(`${task.path} ${task.taskPreset} preset artifact index missing ${resource.index.id}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
for (const requiredRead of presetPackage.context?.requiredReads || []) {
|
|
37
|
-
const expectedPath = expectedReferencePaths.get(requiredRead);
|
|
38
|
-
if (!referenceRows.some((row) => row.ID === requiredRead && (!expectedPath || row.Path === expectedPath))) {
|
|
39
|
-
failures.push(`${task.path} ${task.taskPreset} preset required read missing from references index: ${requiredRead}`);
|
|
40
|
-
}
|
|
41
|
-
if (!requiredReadRows.some((row) => row.Reference === requiredRead && (!expectedPath || row.Path === expectedPath))) {
|
|
42
|
-
failures.push(`${task.path} ${task.taskPreset} preset required read missing from task plan: ${requiredRead}`);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return failures;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function hasIndexedResource(rows, id, expectedPath) {
|
|
49
|
-
return rows.some((row) => row.ID === id && row.Path === expectedPath);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function parseMarkdownRows(markdown, requiredColumns) {
|
|
53
|
-
const rows = [];
|
|
54
|
-
const lines = String(markdown || "").split(/\r?\n/);
|
|
55
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
56
|
-
const line = lines[index].trim();
|
|
57
|
-
const next = lines[index + 1]?.trim() || "";
|
|
58
|
-
if (!isTableRow(line) || !isTableSeparator(next)) continue;
|
|
59
|
-
const header = splitTableRow(line);
|
|
60
|
-
if (!requiredColumns.every((column) => header.includes(column))) continue;
|
|
61
|
-
index += 2;
|
|
62
|
-
while (index < lines.length && isTableRow(lines[index].trim())) {
|
|
63
|
-
const cells = splitTableRow(lines[index].trim());
|
|
64
|
-
if (cells.length === header.length) rows.push(Object.fromEntries(header.map((column, cellIndex) => [column, cells[cellIndex] || ""])));
|
|
65
|
-
index += 1;
|
|
66
|
-
}
|
|
67
|
-
index -= 1;
|
|
68
|
-
}
|
|
69
|
-
return rows;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function isTableRow(line) {
|
|
73
|
-
return line.startsWith("|") && line.endsWith("|");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function isTableSeparator(line) {
|
|
77
|
-
if (!isTableRow(line)) return false;
|
|
78
|
-
return splitTableRow(line).every((cell) => /^:?-{3,}:?$/.test(cell));
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function splitTableRow(line) {
|
|
82
|
-
return line.slice(1, -1).split("|").map((cell) => cell.trim());
|
|
83
|
-
}
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { toPosix } from "./core-shared.mjs";
|
|
5
|
-
|
|
6
|
-
export class ReviewConfirmGitGateError extends Error {
|
|
7
|
-
constructor(message, { code = "review-confirm-git-gate-failed", status = 409, details = {}, recovery = [] } = {}) {
|
|
8
|
-
super(message);
|
|
9
|
-
this.name = "ReviewConfirmGitGateError";
|
|
10
|
-
this.code = code;
|
|
11
|
-
this.status = status;
|
|
12
|
-
this.details = details;
|
|
13
|
-
this.recovery = recovery;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function prepareReviewConfirmGitGate(projectRoot, allowedFilesAbs) {
|
|
18
|
-
const root = path.resolve(projectRoot);
|
|
19
|
-
const resolvedGitRoot = requireGitRoot(root);
|
|
20
|
-
if (real(resolvedGitRoot) !== real(root)) {
|
|
21
|
-
throw new ReviewConfirmGitGateError("Target must be the Git repository root for review confirmation auto-commit.", {
|
|
22
|
-
code: "git-root-mismatch",
|
|
23
|
-
details: { targetRoot: root, gitRoot: resolvedGitRoot },
|
|
24
|
-
recovery: [
|
|
25
|
-
"Run review-confirm from the repository root for the target task.",
|
|
26
|
-
"For private harness tasks, run against the private harness repository root, not the public parent.",
|
|
27
|
-
],
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
const gitRoot = root;
|
|
31
|
-
const allowedPaths = allowedFilesAbs.map((filePath) => toPosix(path.relative(gitRoot, path.resolve(filePath))));
|
|
32
|
-
assertAllowedPaths(allowedPaths);
|
|
33
|
-
assertCleanWorkingTree(gitRoot);
|
|
34
|
-
assertCommitIdentity(gitRoot);
|
|
35
|
-
return { gitRoot, allowedPaths };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function commitReviewConfirmationGate(gate, { taskId, reviewPath, writeFinalAudit, message = "" }) {
|
|
39
|
-
const subjectSuffix = taskId.replace(/[^A-Za-z0-9._/-]+/g, "-");
|
|
40
|
-
assertOnlyAllowedChanged(gate.gitRoot, gate.allowedPaths);
|
|
41
|
-
git(gate.gitRoot, ["add", "--", ...gate.allowedPaths]);
|
|
42
|
-
assertOnlyAllowedStaged(gate.gitRoot, gate.allowedPaths);
|
|
43
|
-
const confirmCommit = commit(gate.gitRoot, `chore: confirm review ${subjectSuffix}`, {
|
|
44
|
-
recovery: [
|
|
45
|
-
"Review confirmation files were written but not committed.",
|
|
46
|
-
`Inspect and either fix hooks then run: git add -- ${gate.allowedPaths.join(" ")} && git commit`,
|
|
47
|
-
"Or manually revert the written review confirmation files if the confirmation should not proceed.",
|
|
48
|
-
],
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
writeFinalAudit(confirmCommit);
|
|
52
|
-
const reviewRelativePath = toPosix(path.relative(gate.gitRoot, path.resolve(reviewPath)));
|
|
53
|
-
git(gate.gitRoot, ["add", "--", reviewRelativePath]);
|
|
54
|
-
assertOnlyAllowedStaged(gate.gitRoot, gate.allowedPaths);
|
|
55
|
-
const auditCommit = commit(gate.gitRoot, `chore: record review confirmation audit ${subjectSuffix}`, {
|
|
56
|
-
recovery: [
|
|
57
|
-
"The confirmation commit was created, but final audit metadata could not be committed.",
|
|
58
|
-
`Confirmation commit SHA: ${confirmCommit}`,
|
|
59
|
-
`Fix hooks, then stage ${reviewRelativePath} and commit the audit metadata.`,
|
|
60
|
-
],
|
|
61
|
-
});
|
|
62
|
-
assertCleanWorkingTree(gate.gitRoot);
|
|
63
|
-
return {
|
|
64
|
-
commitSha: confirmCommit,
|
|
65
|
-
auditCommitSha: auditCommit,
|
|
66
|
-
auditStatus: "committed",
|
|
67
|
-
allowedPaths: gate.allowedPaths,
|
|
68
|
-
message,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function validateReviewConfirmationGitAudit({ projectRoot, taskId, reviewPath, progressPath, commitSha }) {
|
|
73
|
-
const issues = [];
|
|
74
|
-
const addIssue = (code) => issues.push(code);
|
|
75
|
-
const root = projectRoot ? path.resolve(projectRoot) : "";
|
|
76
|
-
const reviewRelativePath = root && reviewPath ? toPosix(path.relative(root, path.resolve(reviewPath))) : "";
|
|
77
|
-
const progressRelativePath = root && progressPath ? toPosix(path.relative(root, path.resolve(progressPath))) : "";
|
|
78
|
-
const expectedPaths = [reviewRelativePath, progressRelativePath].filter(Boolean).sort();
|
|
79
|
-
if (!root) addIssue("git-audit-context-missing");
|
|
80
|
-
if (!commitSha) addIssue("git-audit-commit-missing");
|
|
81
|
-
if (issues.length > 0) return { valid: false, issues };
|
|
82
|
-
|
|
83
|
-
const gitRootResult = git(root, ["rev-parse", "--show-toplevel"], { allowFailure: true });
|
|
84
|
-
if (gitRootResult.status !== 0) {
|
|
85
|
-
return { valid: false, issues: ["git-audit-repository-missing"] };
|
|
86
|
-
}
|
|
87
|
-
const gitRoot = path.resolve(gitRootResult.stdout.trim());
|
|
88
|
-
if (real(gitRoot) !== real(root)) addIssue("git-audit-root-mismatch");
|
|
89
|
-
|
|
90
|
-
const commitResult = git(root, ["rev-parse", "--verify", `${commitSha}^{commit}`], { allowFailure: true });
|
|
91
|
-
if (commitResult.status !== 0) {
|
|
92
|
-
return { valid: false, issues: [...issues, "git-audit-commit-missing"] };
|
|
93
|
-
}
|
|
94
|
-
const fullCommitSha = commitResult.stdout.trim();
|
|
95
|
-
const reachable = git(root, ["merge-base", "--is-ancestor", fullCommitSha, "HEAD"], { allowFailure: true });
|
|
96
|
-
if (reachable.status !== 0) addIssue("git-audit-commit-not-reachable");
|
|
97
|
-
|
|
98
|
-
const subject = git(root, ["show", "-s", "--format=%s", fullCommitSha], { allowFailure: true }).stdout.trim();
|
|
99
|
-
const expectedSubject = `chore: confirm review ${String(taskId || "").replace(/[^A-Za-z0-9._/-]+/g, "-")}`;
|
|
100
|
-
if (subject !== expectedSubject) addIssue("git-audit-subject-mismatch");
|
|
101
|
-
|
|
102
|
-
const changedPaths = git(root, ["diff-tree", "--no-commit-id", "--name-only", "-r", fullCommitSha], { allowFailure: true }).stdout
|
|
103
|
-
.split(/\r?\n/)
|
|
104
|
-
.filter(Boolean)
|
|
105
|
-
.map(toPosix)
|
|
106
|
-
.sort();
|
|
107
|
-
if (expectedPaths.length === 0) addIssue("git-audit-allowlist-missing");
|
|
108
|
-
if (changedPaths.join("\n") !== expectedPaths.join("\n")) addIssue("git-audit-allowlist-mismatch");
|
|
109
|
-
|
|
110
|
-
return {
|
|
111
|
-
valid: issues.length === 0,
|
|
112
|
-
issues,
|
|
113
|
-
commitSha: fullCommitSha,
|
|
114
|
-
changedPaths,
|
|
115
|
-
expectedPaths,
|
|
116
|
-
subject,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function requireGitRoot(root) {
|
|
121
|
-
const result = git(root, ["rev-parse", "--show-toplevel"], { allowFailure: true });
|
|
122
|
-
if (result.status !== 0) {
|
|
123
|
-
throw new ReviewConfirmGitGateError("Review confirmation auto-commit requires a Git repository.", {
|
|
124
|
-
code: "git-repository-missing",
|
|
125
|
-
details: { root, stderr: result.stderr.trim() },
|
|
126
|
-
recovery: ["Initialize Git for the target project or run review-confirm from the correct repository root."],
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
return path.resolve(result.stdout.trim());
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function assertAllowedPaths(paths) {
|
|
133
|
-
const disallowed = paths.filter((relativePath) => {
|
|
134
|
-
if (!relativePath || relativePath.startsWith("../") || path.isAbsolute(relativePath)) return true;
|
|
135
|
-
if (relativePath === "AGENTS.md" || relativePath === "CLAUDE.md") return true;
|
|
136
|
-
if (relativePath === "docs" || relativePath.startsWith("docs/")) return false;
|
|
137
|
-
if (relativePath === ".harness-private" || relativePath.startsWith(".harness-private/")) return true;
|
|
138
|
-
return false;
|
|
139
|
-
});
|
|
140
|
-
if (disallowed.length > 0) {
|
|
141
|
-
throw new ReviewConfirmGitGateError("Review confirmation write allowlist contains forbidden paths.", {
|
|
142
|
-
code: "git-allowlist-forbidden-path",
|
|
143
|
-
details: { disallowed },
|
|
144
|
-
recovery: ["Limit review-confirm writes to the current task INDEX.md file."],
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function assertCleanWorkingTree(gitRoot) {
|
|
150
|
-
const entries = statusEntries(gitRoot);
|
|
151
|
-
if (entries.length > 0) {
|
|
152
|
-
throw new ReviewConfirmGitGateError("Git working tree is not clean; refusing review confirmation auto-commit.", {
|
|
153
|
-
code: "git-dirty-working-tree",
|
|
154
|
-
details: { entries },
|
|
155
|
-
recovery: [
|
|
156
|
-
"Commit, move, or intentionally discard unrelated changes before review-confirm.",
|
|
157
|
-
"Do not stash/reset automatically; resolve ownership of the dirty files first.",
|
|
158
|
-
],
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function assertCommitIdentity(gitRoot) {
|
|
164
|
-
const name = git(gitRoot, ["config", "--get", "user.name"], { allowFailure: true }).stdout.trim();
|
|
165
|
-
const email = git(gitRoot, ["config", "--get", "user.email"], { allowFailure: true }).stdout.trim();
|
|
166
|
-
if (!name || !email) {
|
|
167
|
-
throw new ReviewConfirmGitGateError("Git commit identity is missing; refusing review confirmation auto-commit.", {
|
|
168
|
-
code: "git-identity-missing",
|
|
169
|
-
details: { hasName: Boolean(name), hasEmail: Boolean(email) },
|
|
170
|
-
recovery: [
|
|
171
|
-
"Set a local Git identity for this repository:",
|
|
172
|
-
"git config user.name \"Your Name\"",
|
|
173
|
-
"git config user.email \"you@example.com\"",
|
|
174
|
-
],
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function assertOnlyAllowedChanged(gitRoot, allowedPaths) {
|
|
180
|
-
const entries = statusEntries(gitRoot);
|
|
181
|
-
const outside = entries.filter((entry) => !allowedPaths.includes(entry.path));
|
|
182
|
-
if (outside.length > 0) {
|
|
183
|
-
throw new ReviewConfirmGitGateError("Review confirmation produced changes outside the write allowlist.", {
|
|
184
|
-
code: "git-allowlist-violation",
|
|
185
|
-
details: { entries, allowedPaths },
|
|
186
|
-
recovery: [
|
|
187
|
-
"Inspect the extra files and do not commit them through review-confirm.",
|
|
188
|
-
"Revert only the unintended review-confirm side effects, then retry.",
|
|
189
|
-
],
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function assertOnlyAllowedStaged(gitRoot, allowedPaths) {
|
|
195
|
-
const entries = statusEntries(gitRoot);
|
|
196
|
-
const stagedOutside = entries.filter((entry) => entry.index !== " " && !allowedPaths.includes(entry.path));
|
|
197
|
-
if (stagedOutside.length > 0) {
|
|
198
|
-
throw new ReviewConfirmGitGateError("Git index contains staged files outside the review confirmation allowlist.", {
|
|
199
|
-
code: "git-index-allowlist-violation",
|
|
200
|
-
details: { stagedOutside, allowedPaths },
|
|
201
|
-
recovery: ["Unstage unrelated files before retrying review-confirm."],
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function commit(gitRoot, message, { recovery }) {
|
|
207
|
-
const result = git(gitRoot, ["commit", "-m", message], { allowFailure: true });
|
|
208
|
-
if (result.status !== 0) {
|
|
209
|
-
throw new ReviewConfirmGitGateError("Git commit failed during review confirmation auto-commit.", {
|
|
210
|
-
code: "git-commit-failed",
|
|
211
|
-
details: { stdout: result.stdout.trim(), stderr: result.stderr.trim() },
|
|
212
|
-
recovery,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
return git(gitRoot, ["rev-parse", "HEAD"]).stdout.trim();
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function statusEntries(gitRoot) {
|
|
219
|
-
const output = git(gitRoot, ["status", "--porcelain=v1", "--untracked-files=all"]).stdout;
|
|
220
|
-
return output.split(/\r?\n/).filter(Boolean).map((line) => ({
|
|
221
|
-
index: line.slice(0, 1),
|
|
222
|
-
worktree: line.slice(1, 2),
|
|
223
|
-
path: parseStatusPath(line.slice(3)),
|
|
224
|
-
raw: line,
|
|
225
|
-
}));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function parseStatusPath(value) {
|
|
229
|
-
const unquoted = value.replace(/^"|"$/g, "");
|
|
230
|
-
const renamed = unquoted.includes(" -> ") ? unquoted.split(" -> ").pop() : unquoted;
|
|
231
|
-
return renamed;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function git(cwd, args, { allowFailure = false } = {}) {
|
|
235
|
-
const result = spawnSync("git", args, { cwd, encoding: "utf8" });
|
|
236
|
-
if (!allowFailure && result.status !== 0) {
|
|
237
|
-
throw new ReviewConfirmGitGateError(`git ${args.join(" ")} failed`, {
|
|
238
|
-
code: "git-command-failed",
|
|
239
|
-
details: { stdout: result.stdout.trim(), stderr: result.stderr.trim() },
|
|
240
|
-
recovery: ["Inspect the Git error and retry review-confirm after resolving it."],
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
return result;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
function real(filePath) {
|
|
247
|
-
return fs.realpathSync(filePath);
|
|
248
|
-
}
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import { normalizeTarget, toPosix } from "./core-shared.mjs";
|
|
3
|
-
import { capabilityDefinitions, readCapabilityRegistry } from "./capability-registry.mjs";
|
|
4
|
-
import { summarizeGitState } from "./git-status-summary.mjs";
|
|
5
|
-
import { collectTasks, taskCutoverCounters } from "./task-scanner.mjs";
|
|
6
|
-
|
|
7
|
-
export function buildStatusData(targetInput, options = {}) {
|
|
8
|
-
const target = targetInput?.projectRoot ? targetInput : normalizeTarget(targetInput);
|
|
9
|
-
const validationMode = options.validationMode || "data-only";
|
|
10
|
-
const gitState = options.gitState || summarizeGitState(target);
|
|
11
|
-
const registry = options.capabilityState?.registry || readCapabilityRegistry(target);
|
|
12
|
-
const detected = options.capabilityState?.detected || [];
|
|
13
|
-
const capabilityWarnings = options.capabilityState?.warnings || [];
|
|
14
|
-
const failures = [...(options.failures || [])];
|
|
15
|
-
const warnings = [...(options.warnings || [])];
|
|
16
|
-
const legacy = options.legacy || { status: "skipped", code: 0, stdout: "", stderr: "" };
|
|
17
|
-
const tasks = options.tasks || collectTasks(target, {
|
|
18
|
-
requireGeneratedScaffoldProvenance: options.requireGeneratedScaffoldProvenance === true,
|
|
19
|
-
taskPlanPaths: options.taskPlanPaths,
|
|
20
|
-
closeoutContent: options.closeoutContent,
|
|
21
|
-
});
|
|
22
|
-
const briefReady = tasks.filter((task) => task.briefSource === "standalone").length;
|
|
23
|
-
const briefMissing = tasks.length - briefReady;
|
|
24
|
-
const capabilityNames = new Map(registry.capabilities.map((capability) => [capability.name, capability]));
|
|
25
|
-
for (const capability of detected) {
|
|
26
|
-
if (!capabilityNames.has(capability)) capabilityNames.set(capability, { name: capability, state: "configured" });
|
|
27
|
-
}
|
|
28
|
-
const cutoverCounters = taskCutoverCounters(tasks);
|
|
29
|
-
const fullCutoverEligible =
|
|
30
|
-
validationMode === "validated" &&
|
|
31
|
-
failures.length === 0 &&
|
|
32
|
-
warnings.length === 0 &&
|
|
33
|
-
cutoverCounters.legacyVisualOnlyCount === 0 &&
|
|
34
|
-
cutoverCounters.unknownClassificationCount === 0 &&
|
|
35
|
-
cutoverCounters.weakBriefCount === 0 &&
|
|
36
|
-
cutoverCounters.missingCanonicalVisualMapCount === 0;
|
|
37
|
-
|
|
38
|
-
return {
|
|
39
|
-
project: {
|
|
40
|
-
name: path.basename(target.projectRoot),
|
|
41
|
-
root: `TARGET:${target.docsOnly ? toPosix(path.relative(target.projectRoot, target.docsRoot)) : "."}`,
|
|
42
|
-
docsOnly: target.docsOnly,
|
|
43
|
-
},
|
|
44
|
-
schemaVersion: 2,
|
|
45
|
-
generatedAt: options.generatedAt || new Date().toISOString(),
|
|
46
|
-
mode: registry.mode,
|
|
47
|
-
checkState: {
|
|
48
|
-
status: failures.length > 0 ? "fail" : warnings.length > 0 ? "warn" : "pass",
|
|
49
|
-
validationMode,
|
|
50
|
-
failures: failures.length,
|
|
51
|
-
warnings: warnings.length,
|
|
52
|
-
details: { failures, warnings },
|
|
53
|
-
legacy,
|
|
54
|
-
},
|
|
55
|
-
git: gitState.summary,
|
|
56
|
-
summary: {
|
|
57
|
-
tasks: tasks.length,
|
|
58
|
-
briefCoverage: {
|
|
59
|
-
ready: briefReady,
|
|
60
|
-
missing: briefMissing,
|
|
61
|
-
total: tasks.length,
|
|
62
|
-
},
|
|
63
|
-
visualMapCoverage: {
|
|
64
|
-
canonical: tasks.filter((task) => task.visualMapSource === "canonical").length,
|
|
65
|
-
legacyOnly: cutoverCounters.legacyVisualOnlyCount,
|
|
66
|
-
missing: tasks.filter((task) => task.visualMapStatus === "missing").length,
|
|
67
|
-
total: tasks.length,
|
|
68
|
-
},
|
|
69
|
-
fullCutoverEligible,
|
|
70
|
-
legacyVisualOnlyCount: cutoverCounters.legacyVisualOnlyCount,
|
|
71
|
-
unknownClassificationCount: cutoverCounters.unknownClassificationCount,
|
|
72
|
-
weakBriefCount: cutoverCounters.weakBriefCount,
|
|
73
|
-
visualMapRequiredCount: cutoverCounters.visualMapRequiredCount,
|
|
74
|
-
missingCanonicalVisualMapCount: cutoverCounters.missingCanonicalVisualMapCount,
|
|
75
|
-
},
|
|
76
|
-
capabilities: [...capabilityNames.values()].map((capability) => ({
|
|
77
|
-
name: capability.name,
|
|
78
|
-
state: capability.state || "configured",
|
|
79
|
-
dependencyStatus: capabilityDefinitions[capability.name]?.dependencies.every((dependency) => capabilityNames.has(dependency))
|
|
80
|
-
? "valid"
|
|
81
|
-
: "invalid",
|
|
82
|
-
warnings: capabilityWarnings.filter((warning) => warning.includes(capability.name)),
|
|
83
|
-
})),
|
|
84
|
-
tasks,
|
|
85
|
-
handoffs: tasks.flatMap((task) => task.handoffs || []),
|
|
86
|
-
recentActivity: tasks.slice(0, 8).map((task) => ({ at: new Date().toISOString(), type: "task", summary: task.title })),
|
|
87
|
-
};
|
|
88
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import path from "node:path";
|
|
2
|
-
import {
|
|
3
|
-
readFileSafe,
|
|
4
|
-
toPosix,
|
|
5
|
-
walkFiles,
|
|
6
|
-
} from "./core-shared.mjs";
|
|
7
|
-
import {
|
|
8
|
-
firstColumn,
|
|
9
|
-
splitMarkdownRow,
|
|
10
|
-
} from "./markdown-utils.mjs";
|
|
11
|
-
|
|
12
|
-
export function validateSubagentAuthorization(target, { strict = true } = {}) {
|
|
13
|
-
const failures = [];
|
|
14
|
-
const warnings = [];
|
|
15
|
-
const report = (message) => {
|
|
16
|
-
if (strict) failures.push(message);
|
|
17
|
-
else warnings.push(`adoption-needed: ${message}`);
|
|
18
|
-
};
|
|
19
|
-
const strategyPaths = walkFiles(target.docsRoot)
|
|
20
|
-
.filter((file) => file.endsWith("execution_strategy.md"))
|
|
21
|
-
.filter((file) => !file.includes(`${path.sep}_task-template${path.sep}`))
|
|
22
|
-
.filter((file) => !file.includes(`${path.sep}_optional-structures${path.sep}`))
|
|
23
|
-
.filter((file) => !file.includes(`${path.sep}_archive${path.sep}`));
|
|
24
|
-
|
|
25
|
-
for (const strategyPath of strategyPaths) {
|
|
26
|
-
const relative = toPosix(path.relative(target.projectRoot, strategyPath));
|
|
27
|
-
const content = readFileSafe(strategyPath);
|
|
28
|
-
const rows = subagentAuthorizationRows(content);
|
|
29
|
-
for (const row of rows.filter((candidate) => /worker/i.test(candidate.role))) {
|
|
30
|
-
if (!isWorkerAuthorizedStatus(row.status)) continue;
|
|
31
|
-
const missing = [];
|
|
32
|
-
for (const [label, value] of [
|
|
33
|
-
["Authorized By", row.authorizedBy],
|
|
34
|
-
["Authorized At", row.authorizedAt],
|
|
35
|
-
["Scope", row.scope],
|
|
36
|
-
["Worktree / Branch", row.worktreeBranch],
|
|
37
|
-
]) {
|
|
38
|
-
if (!isConcreteAuthorizationValue(value)) missing.push(label);
|
|
39
|
-
}
|
|
40
|
-
if (missing.length > 0) report(`${relative} worker subagent authorization is incomplete: ${missing.join(", ")}`);
|
|
41
|
-
}
|
|
42
|
-
const delegation = subagentDelegationRows(content).find((row) => /worker/i.test(row.question));
|
|
43
|
-
if (delegation && normalizeDecision(delegation.decision) === "ask-user") {
|
|
44
|
-
const userDecision = userAuthorizationRows(content).reverse().find((row) => /worker/i.test(row.gate) && isResolvedWorkerGateState(row.state));
|
|
45
|
-
if (!userDecision) {
|
|
46
|
-
report(`${relative} worker subagent ask-user decision is unresolved: missing User Authorization Decision`);
|
|
47
|
-
} else {
|
|
48
|
-
const missing = missingUserDecisionFields(userDecision);
|
|
49
|
-
if (missing.length > 0) report(`${relative} worker subagent authorization decision is incomplete: ${missing.join(", ")}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return { failures, warnings };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function subagentAuthorizationRows(content) {
|
|
57
|
-
const section = markdownSection(content, "Subagent Authorization");
|
|
58
|
-
if (!section) return [];
|
|
59
|
-
const lines = section.split(/\r?\n/);
|
|
60
|
-
for (let index = 0; index < lines.length - 1; index += 1) {
|
|
61
|
-
if (!lines[index].trim().startsWith("|")) continue;
|
|
62
|
-
const header = splitMarkdownRow(lines[index]);
|
|
63
|
-
const separator = splitMarkdownRow(lines[index + 1]);
|
|
64
|
-
if (!separator.every((cell) => /^:?-{3,}:?$/.test(cell))) continue;
|
|
65
|
-
const roleIndex = firstColumn(header, ["Role"]);
|
|
66
|
-
const statusIndex = firstColumn(header, ["Status"]);
|
|
67
|
-
if (roleIndex < 0 || statusIndex < 0) continue;
|
|
68
|
-
const indexes = {
|
|
69
|
-
role: roleIndex,
|
|
70
|
-
status: statusIndex,
|
|
71
|
-
authorizedBy: firstColumn(header, ["Authorized By"]),
|
|
72
|
-
authorizedAt: firstColumn(header, ["Authorized At"]),
|
|
73
|
-
scope: firstColumn(header, ["Scope"]),
|
|
74
|
-
worktreeBranch: firstColumn(header, ["Worktree / Branch", "Worktree", "Branch"]),
|
|
75
|
-
};
|
|
76
|
-
return lines
|
|
77
|
-
.slice(index + 2)
|
|
78
|
-
.filter((line) => line.trim().startsWith("|"))
|
|
79
|
-
.map(splitMarkdownRow)
|
|
80
|
-
.filter((row) => row.length === header.length)
|
|
81
|
-
.map((row) => Object.fromEntries(Object.entries(indexes).map(([key, column]) => [key, column >= 0 ? row[column] || "" : ""])));
|
|
82
|
-
}
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function subagentDelegationRows(content) {
|
|
87
|
-
const section = markdownSection(content, "Subagent Delegation Decision");
|
|
88
|
-
if (!section) return [];
|
|
89
|
-
return parseFirstTable(section, ["Question", "Decision"]).map((row) => ({
|
|
90
|
-
question: row.Question || "",
|
|
91
|
-
decision: row.Decision || "",
|
|
92
|
-
}));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function userAuthorizationRows(content) {
|
|
96
|
-
return parseMatchingTables(content, ["Gate", "State"]).map((row) => ({
|
|
97
|
-
gate: row.Gate || "",
|
|
98
|
-
state: row.State || "",
|
|
99
|
-
decidedBy: row["Decided By"] || "",
|
|
100
|
-
decidedAt: row["Decided At"] || "",
|
|
101
|
-
scope: row.Scope || "",
|
|
102
|
-
worktreeBranch: row["Worktree / Branch"] || row.Worktree || row.Branch || "",
|
|
103
|
-
}));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function parseMatchingTables(content, requiredColumns) {
|
|
107
|
-
const lines = String(content || "").split(/\r?\n/);
|
|
108
|
-
const rows = [];
|
|
109
|
-
for (let index = 0; index < lines.length - 1; index += 1) {
|
|
110
|
-
if (!lines[index].trim().startsWith("|")) continue;
|
|
111
|
-
const header = splitMarkdownRow(lines[index]);
|
|
112
|
-
const separator = splitMarkdownRow(lines[index + 1]);
|
|
113
|
-
if (!separator.every((cell) => /^:?-{3,}:?$/.test(cell))) continue;
|
|
114
|
-
if (requiredColumns.some((column) => firstColumn(header, [column]) < 0)) continue;
|
|
115
|
-
let rowIndex = index + 2;
|
|
116
|
-
while (rowIndex < lines.length && lines[rowIndex].trim().startsWith("|")) {
|
|
117
|
-
const row = splitMarkdownRow(lines[rowIndex]);
|
|
118
|
-
if (row.length === header.length) rows.push(Object.fromEntries(header.map((column, columnIndex) => [column, row[columnIndex] || ""])));
|
|
119
|
-
rowIndex += 1;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return rows;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function parseFirstTable(content, requiredColumns) {
|
|
126
|
-
const lines = String(content || "").split(/\r?\n/);
|
|
127
|
-
for (let index = 0; index < lines.length - 1; index += 1) {
|
|
128
|
-
if (!lines[index].trim().startsWith("|")) continue;
|
|
129
|
-
const header = splitMarkdownRow(lines[index]);
|
|
130
|
-
const separator = splitMarkdownRow(lines[index + 1]);
|
|
131
|
-
if (!separator.every((cell) => /^:?-{3,}:?$/.test(cell))) continue;
|
|
132
|
-
if (requiredColumns.some((column) => firstColumn(header, [column]) < 0)) continue;
|
|
133
|
-
return lines
|
|
134
|
-
.slice(index + 2)
|
|
135
|
-
.filter((line) => line.trim().startsWith("|"))
|
|
136
|
-
.map(splitMarkdownRow)
|
|
137
|
-
.filter((row) => row.length === header.length)
|
|
138
|
-
.map((row) => Object.fromEntries(header.map((column, columnIndex) => [column, row[columnIndex] || ""])));
|
|
139
|
-
}
|
|
140
|
-
return [];
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function markdownSection(content, heading) {
|
|
144
|
-
const lines = String(content || "").split(/\r?\n/);
|
|
145
|
-
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
146
|
-
const start = lines.findIndex((line) => new RegExp(`^##\\s+${escaped}\\s*$`, "i").test(line.trim()));
|
|
147
|
-
if (start < 0) return "";
|
|
148
|
-
const end = lines.findIndex((line, index) => index > start && /^##\s+/.test(line));
|
|
149
|
-
return lines.slice(start + 1, end < 0 ? undefined : end).join("\n");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function isConcreteAuthorizationValue(value) {
|
|
153
|
-
const raw = String(value || "").replace(/`/g, "").trim();
|
|
154
|
-
return Boolean(raw) && !/^\[.*\]$/.test(raw) && !/^(pending|n\/a|na|none|-|—|–|待授权|待定|无)$/i.test(raw);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function isWorkerAuthorizedStatus(value) {
|
|
158
|
-
const raw = String(value || "")
|
|
159
|
-
.replace(/`/g, "")
|
|
160
|
-
.trim()
|
|
161
|
-
.toLowerCase()
|
|
162
|
-
.replaceAll("_", "-")
|
|
163
|
-
.replace(/\s+/g, "-");
|
|
164
|
-
if (!raw || /^(not-authorized|unauthorized|pending|no|false|未授权|待授权)$/.test(raw)) return false;
|
|
165
|
-
return /(^|\b)(authorized|used|active|approved)(\b|$)|已授权|已使用/.test(raw);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function normalizeDecision(value) {
|
|
169
|
-
return String(value || "")
|
|
170
|
-
.replace(/`/g, "")
|
|
171
|
-
.trim()
|
|
172
|
-
.toLowerCase()
|
|
173
|
-
.replaceAll("_", "-")
|
|
174
|
-
.replace(/\s+/g, "-");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function isResolvedWorkerGateState(value) {
|
|
178
|
-
const raw = normalizeDecision(value);
|
|
179
|
-
return /^(authorized|approved|denied|rejected|not-needed|not-authorized|no|yes|无需|拒绝|已授权)$/.test(raw);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function missingUserDecisionFields(row) {
|
|
183
|
-
const state = normalizeDecision(row.state);
|
|
184
|
-
const required = state === "authorized" || state === "approved" || state === "yes" || state === "已授权"
|
|
185
|
-
? [
|
|
186
|
-
["Decided By", row.decidedBy],
|
|
187
|
-
["Decided At", row.decidedAt],
|
|
188
|
-
["Scope", row.scope],
|
|
189
|
-
["Worktree / Branch", row.worktreeBranch],
|
|
190
|
-
]
|
|
191
|
-
: [
|
|
192
|
-
["Decided By", row.decidedBy],
|
|
193
|
-
["Decided At", row.decidedAt],
|
|
194
|
-
];
|
|
195
|
-
return required.filter(([, value]) => !isConcreteAuthorizationValue(value)).map(([label]) => label);
|
|
196
|
-
}
|