@tea-agent/loop-agent 0.44.0-next.12 → 0.44.0-next.14
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 +27 -2
- package/README.md +12 -12
- package/dist/adapters/loop-agent.js +34 -0
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +5 -5
- package/dist/cli/program.js +1 -4
- package/dist/commands/init-legacy-audit.js +330 -0
- package/dist/commands/init-upgrade.js +644 -104
- package/dist/commands/init.js +686 -330
- package/dist/commands/operator-guide.js +99 -0
- package/dist/commands/operator.js +16 -3
- package/dist/executors/dag-pi-executor.js +11 -6
- package/dist/executors/pi-sdk-executor.js +3 -1
- package/dist/executors/pi-writer-tool-policy.js +60 -4
- package/dist/executors/playwright-cli-launcher.js +31 -45
- package/dist/executors/shell-write-guard.js +24 -67
- package/dist/governance/exec-plans.js +10 -1
- package/dist/governance/harness.js +2 -7
- package/dist/governance/manifest-types.js +28 -9
- package/dist/governance/project-capability-scanner.js +263 -0
- package/dist/governance/project-config.js +96 -0
- package/dist/governance/resource-catalog.js +151 -0
- package/dist/infrastructure/console/app-data.js +6 -2
- package/dist/shared/operator/capabilities.js +2 -2
- package/dist/shared/package-metadata.js +23 -7
- package/dist/shared/pi-context-pressure/extension.js +23 -0
- package/dist/shared/pi-context-pressure/index.js +1 -0
- package/dist/shared/pi-context-pressure/telemetry.js +4 -0
- package/dist/shared/pi-context-pressure/tool-result-pruner.js +98 -0
- package/dist/shared/update/npm-client.js +49 -1
- package/dist/task/config-types.js +4 -2
- package/dist/task/source-prepare/build-draft.js +1 -0
- package/dist/task/source-prepare/path-policy.js +17 -7
- package/dist/worker/cli.js +3 -5
- package/dist/worker/console/chat/browser-automation.js +455 -41
- package/dist/worker/console/chat/browser-content.js +74 -0
- package/dist/worker/console/chat/browser-diagnostics.js +250 -0
- package/dist/worker/console/chat/browser-evaluation.js +58 -0
- package/dist/worker/console/chat/browser-operation-queue.js +19 -0
- package/dist/worker/console/chat/browser-pick.js +113 -0
- package/dist/worker/console/chat/browser-routes.js +54 -5
- package/dist/worker/console/chat/browser-screenshot-result.js +47 -0
- package/dist/worker/console/chat/browser-storage.js +89 -0
- package/dist/worker/console/chat/chat-event-store.js +7 -6
- package/dist/worker/console/chat/context-insights.js +84 -19
- package/dist/worker/console/chat/pet/pet-affinity.js +169 -0
- package/dist/worker/console/chat/pet/pet-persist.js +109 -0
- package/dist/worker/console/chat/pet/pet-registry.js +142 -0
- package/dist/worker/console/chat/pet/pet-remarks.js +48 -0
- package/dist/worker/console/chat/pet/pet-routes.js +125 -0
- package/dist/worker/console/chat/pet/pet-service.js +262 -0
- package/dist/worker/console/chat/pet/pet-state.js +99 -0
- package/dist/worker/console/chat/pi-mode-loop-isolation.js +17 -2
- package/dist/worker/console/chat/pi-runtime/custom-tools/browser-tools.js +125 -174
- package/dist/worker/console/chat/pi-runtime.js +8 -3
- package/dist/worker/console/chat/repo-browser.js +80 -2
- package/dist/worker/console/chat/repo-preview-kind.js +155 -0
- package/dist/worker/console/chat/routes.js +43 -7
- package/dist/worker/console/chat/scheduled-goal-request.js +14 -6
- package/dist/worker/console/chat/session-mode.js +1 -1
- package/dist/worker/console/chat/session-store.js +24 -5
- package/dist/worker/console/chat/tools.js +12 -0
- package/dist/worker/console/console-shutdown.js +34 -0
- package/dist/worker/console/server.js +2 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-r1NAPU_5.js → abnfDiagram-N423BO3Z-DASc69AM.js} +1 -1
- package/dist/worker/console/static/assets/{arc-dT5T4sVL.js → arc-h09p-LAc.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CP_cOq46.js → architectureDiagram-T3A2C74G-CZ3dtLkh.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-sToEZxEf.js → blockDiagram-VBNYF7ZC-DMrRhMXG.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DSxwwi2m.js → c4Diagram-5PPSVZJV-DQxisHV6.js} +1 -1
- package/dist/worker/console/static/assets/channel-D1zgKZfg.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-B9fh0IkF.js → chunk-2GRJ4B5K-CefVN9zh.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-CAUNl-A6.js → chunk-2Q5K7J3B-DmR1Koq2.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-BomeqbNX.js → chunk-5RXB4S5H-NlzrSW7L.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-BNAuTOLE.js → chunk-5VM5RSS4-DfelJv-0.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DfzjRD5y.js → chunk-6Q2QTUOP-BK6RFy58.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C3uE-yc2.js → chunk-GF5L2VYU-BAeh9lz4.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-C3sQMMYr.js → chunk-JWPE2WC7-ChPS5WzG.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-D_AtEYQL.js → chunk-KBJHAD2P-BoVe5IM6.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DW4TwhKv.js → chunk-RYQCIY6F-C4q_0Lw9.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CTQDmJcp.js → chunk-XXDRQBXY-Bb9TvjeY.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-CXNNsY2M.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-CXNNsY2M.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-iMqPuBsL.js → cose-bilkent-JH36ORCC-DmNnL8Ff.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-DktIuDWs.js → cynefin-VYW2F7L2-Contvx58.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BgVnjyEs.js → cynefinDiagram-MW4NZA55-Cigq7cjk.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-CR25E4bu.js → dagre-VZM6K2ZE-dEbC0nxb.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-CPM2h5HY.js → diagram-7IWD3JNH-CzIztESV.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-DqF0x2g1.js → diagram-B4RE2ZJO-Bo14zbaY.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-G8BYp2eb.js → diagram-LBJQPF4R-C4xUyil6.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-BbC4kT_D.js → diagram-Q27KOJAE-CaBY_uYa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-CKZwZ-G5.js → diagram-UB23O5K3-ClUXUeiu.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-kJDmheia.js → ebnfDiagram-BXEA7PRR-BbGCgjC6.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-DFTVHyxB.js → erDiagram-JOGREHBK-Dr-AcwSl.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-BqdN5_os.js → flowDiagram-UKHOOZJN-BG8Gcfyc.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-SYDjVakh.js → ganttDiagram-PKOTCBZU-CFVaPs5r.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bf8bNpGA.js → gitGraphDiagram-DS77QQ5N-ChJ8pm-O.js} +1 -1
- package/dist/worker/console/static/assets/index-B_5GhVIO.js +488 -0
- package/dist/worker/console/static/assets/index-C7YMBluX.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DIeAmJ45.js → infoDiagram-6WML65LV-C5J9-uCZ.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-oikqUAlo.js → ishikawaDiagram-WSZJBQD7-BcfjqzDv.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-mKEF_xYT.js → journeyDiagram-NVQOT4AX-BzzCO-rD.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-BdWJHPnN.js → kanban-definition-27J2QSJJ-NCHUZ46h.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DGRlieMH.js → linear-BKZlWCUU.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-4P9yPC9c.js → mermaid.core-DbTaQjFE.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-5PTSkos6.js → mindmap-definition-FAOFIHXS-CroYl5xQ.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DONIFpG5.js → pegDiagram-VL7TDLO6-DgMBx-9k.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-D_Z4S70M.js → pieDiagram-7S7Q4E2Y-CyWDmbq1.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CAsjh8iY.js → quadrantDiagram-CIZ2JOQS-BgGEOqSP.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Cx4Dd6ac.js → railroadDiagram-AXF67PYL-BMZ7oeeV.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-cQb7mwyH.js → requirementDiagram-LRYGKXZP-DxqyGhcc.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-Cd-DhUdf.js → sankeyDiagram-W5VNT64P-DnSvNCG8.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-AlKYRgIk.js → sequenceDiagram-SI44F4Z6-BiZh6f-R.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-CUy_ET4e.js → sizeCapture-X5ZJPWSS-BKXSI8WB.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-CTuVe6Sa.js → stateDiagram-OKZ733FA-CfSgoYFm.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DgHi3vjb.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-ChFLJb-v.js → swimlanes-SLNWSIFB-CyGADcHe.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Bgvdt1Y0.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DZZeiAPr.js → timeline-definition-Z64GVDOM-HmPaSPOK.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-t4O91cZU.js → vennDiagram-T6HMQDX7-VA2VBbez.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CV5qGHZu.js → wardleyDiagram-T6FBY63Y-BxjiBPZa.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-DyXbytUI.js → xychartDiagram-ELKLHX3M--hUY-qFj.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static/pets/ember/pet.json +167 -0
- package/dist/worker/console/static/pets/ember/spritesheet.png +0 -0
- package/dist/worker/console/static-src/operator-chat/details-rail-surfaces.js +4 -3
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +57 -9
- package/dist/worker/console/static-src/operator-chat/use-stat-dialog.js +95 -0
- package/dist/worker/console/static-src/operator-chat/useRepoBrowser.js +6 -0
- package/dist/worker/console/workspace-context.js +15 -0
- package/dist/worker/console/workspace-initialization.js +25 -16
- package/dist/worker/delivery/package.js +4 -1
- package/dist/worker/materialize/harness-task-lineage.js +4 -2
- package/dist/worker/observe/static/console-theme.css +33 -29
- package/dist/worker/observe/static/operator-chrome.js +1 -42
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +3 -1
- package/dist/workflows/dag/backend-test-layout.js +27 -7
- package/dist/workflows/dag/backend-test-markdown-workflow.js +3 -1
- package/dist/workflows/dag/backend-test-pytest-collection.js +4 -1
- package/dist/workflows/dag/final-verification.js +45 -10
- package/dist/workflows/dag/frontend-test-case-checklist.js +12 -3
- package/dist/workflows/dag/frontend-test-environment-probe.js +0 -2
- package/dist/workflows/dag/frontend-test-layout.js +41 -12
- package/dist/workflows/dag/hybrid/sources.js +35 -16
- package/dist/workflows/dag/hybrid/templates/backend-test.js +70 -55
- package/dist/workflows/dag/hybrid/templates/frontend-test.js +62 -41
- package/dist/workflows/dag/hybrid/templates/kg-bootstrap.js +6 -6
- package/dist/workflows/dag/hybrid/templates/knowledge-sync.js +5 -5
- package/dist/workflows/dag/skill-instructions.js +102 -32
- package/dist/workflows/dag/skill-snapshot.js +3 -0
- package/dist/workflows/dag/skills.js +7 -7
- package/dist/workflows/dag/test-artifact-namespace.js +73 -0
- package/dist/workflows/dag/validate.js +24 -5
- package/dist/workflows/dag/workspace-checkpoint.js +13 -1
- package/docs/architecture/runtime-boundaries.md +28 -22
- package/docs/init-surface.manifest.json +43 -295
- package/docs/templates/README.md +4 -4
- package/docs/templates/agent-dag.base.json +9 -9
- package/docs/templates/agent-dag.final-verification.json +6 -6
- package/docs/templates/agent-dag.supervised-implementation.json +12 -12
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +3 -3
- package/docs/templates/backend-test-dag.json +708 -708
- package/docs/templates/backend-test-dag.review-cases.prompt.md +3 -3
- package/docs/templates/frontend-implementation-dag.json +2 -2
- package/docs/templates/frontend-test-case-checklist.md +2 -2
- package/docs/templates/frontend-test-dag.generate-cases.prompt.md +7 -7
- package/docs/templates/frontend-test-dag.json +48 -39
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +2 -2
- package/docs/templates/frontend-test-dag.retrospect.prompt.md +1 -1
- package/docs/templates/frontend-test-dag.review-cases.prompt.md +1 -1
- package/docs/templates/hybrid-dag.json +9 -9
- package/docs/templates/knowledge-sync-dag.json +8 -8
- package/package.json +12 -3
- package/resource-catalog.v1.json +1973 -0
- package/resources/agent-bridge/loop-agent/SKILL.md +40 -0
- package/resources/init-history/legacy-surface.v1.json +33 -0
- package/skills/fe-test-ui-scout/SKILL.md +4 -4
- package/skills/fe-test-ui-scout/references/ledger-schema.md +1 -1
- package/skills/init-update/SKILL.md +34 -0
- package/skills/init-update/agents/openai.yaml +6 -0
- package/skills/playwright-cli/SKILL.md +1 -1
- package/skills/playwright-cli-case-generator/SKILL.md +4 -4
- package/dist/worker/console/static/assets/channel-CyBY_yGk.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-D5lN4E_E.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-D5lN4E_E.js +0 -1
- package/dist/worker/console/static/assets/index-B28Onmfy.css +0 -1
- package/dist/worker/console/static/assets/index-CTMZsDmh.js +0 -486
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-BsWhs8O7.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CJcioYGH.js +0 -8
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { checkInitUpdate } from "./init.js";
|
|
4
|
+
import { loadNormalizedProjectConfig } from "../governance/project-config.js";
|
|
5
|
+
import { CONTROLLER_RESOURCE_IDS, createControllerContextProvider, } from "../governance/resource-catalog.js";
|
|
6
|
+
import { LOOP_AGENT_PACKAGE_NAME, readPackageVersion } from "../shared/package-metadata.js";
|
|
7
|
+
async function exists(file) {
|
|
8
|
+
try {
|
|
9
|
+
await access(file);
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export async function buildOperatorGuideDocument(repoRootInput) {
|
|
17
|
+
const repoRoot = path.resolve(repoRootInput);
|
|
18
|
+
const provider = await createControllerContextProvider();
|
|
19
|
+
const packageRoot = path.dirname(provider.list().length > 0
|
|
20
|
+
? (await provider.readText(CONTROLLER_RESOURCE_IDS.loopAgentBridgeSkill)).absolutePath
|
|
21
|
+
: repoRoot);
|
|
22
|
+
const version = readPackageVersion(path.resolve(packageRoot, "..", "..", "..")) ?? "unknown";
|
|
23
|
+
let reproducibility = "unavailable";
|
|
24
|
+
try {
|
|
25
|
+
reproducibility = (await loadNormalizedProjectConfig(repoRoot)).verification.reproducibility;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// An uninitialized or malformed project is classified below by init assessment.
|
|
29
|
+
}
|
|
30
|
+
let status = "uninitialized";
|
|
31
|
+
let bridge = "missing";
|
|
32
|
+
let next = {
|
|
33
|
+
command: "loop-agent init --repo-root .",
|
|
34
|
+
reason: "initialize the minimal project surface",
|
|
35
|
+
};
|
|
36
|
+
if (await exists(path.join(repoRoot, "harness.json"))) {
|
|
37
|
+
const report = await checkInitUpdate({ repoRoot });
|
|
38
|
+
const bridgeDecision = report.humanDecisions.find((item) => item.reason.includes("bridge-path-conflict"));
|
|
39
|
+
bridge = bridgeDecision
|
|
40
|
+
? "conflict"
|
|
41
|
+
: await exists(path.join(repoRoot, ".agents", "skills", "loop-agent", "SKILL.md"))
|
|
42
|
+
? "ready"
|
|
43
|
+
: "missing";
|
|
44
|
+
if (report.humanDecisions.length > 0 || report.modelMergeTasks.length > 0) {
|
|
45
|
+
status = "blocked";
|
|
46
|
+
next = {
|
|
47
|
+
command: "loop-agent init upgrade --repo-root . --json",
|
|
48
|
+
reason: "complete the bounded migration decisions before starting new work",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
else if (report.deterministicActions.length > 0) {
|
|
52
|
+
status = "needs-update";
|
|
53
|
+
next = {
|
|
54
|
+
command: "loop-agent init update --repo-root . --apply-safe --json",
|
|
55
|
+
reason: "apply the deterministic minimal-surface update",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
status = "ready";
|
|
60
|
+
next = {
|
|
61
|
+
command: "loop-agent task advance <task-id> --profile auto --json",
|
|
62
|
+
reason: "prepare or continue a governed task",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
schemaVersion: 1,
|
|
68
|
+
kind: "loop-agent-operator-guide",
|
|
69
|
+
controller: {
|
|
70
|
+
packageName: LOOP_AGENT_PACKAGE_NAME,
|
|
71
|
+
version,
|
|
72
|
+
resourceCatalogSha256: provider.identity.catalogDigest,
|
|
73
|
+
},
|
|
74
|
+
project: { repoRoot, status, configReproducibility: reproducibility, bridge },
|
|
75
|
+
dag: {
|
|
76
|
+
entry: "loop-agent task advance",
|
|
77
|
+
longRunningOwner: "console-operation-required",
|
|
78
|
+
builtinSkillSource: "builtin",
|
|
79
|
+
},
|
|
80
|
+
next,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
export function renderOperatorGuideMarkdown(document) {
|
|
84
|
+
return [
|
|
85
|
+
"# loop-agent operator guide",
|
|
86
|
+
"",
|
|
87
|
+
`- Controller: \`${document.controller.packageName}@${document.controller.version}\``,
|
|
88
|
+
`- Project: \`${document.project.status}\``,
|
|
89
|
+
`- Bridge: \`${document.project.bridge}\``,
|
|
90
|
+
`- Verification: \`${document.project.configReproducibility}\``,
|
|
91
|
+
"",
|
|
92
|
+
"## Next",
|
|
93
|
+
"",
|
|
94
|
+
`\`${document.next.command}\``,
|
|
95
|
+
"",
|
|
96
|
+
document.next.reason,
|
|
97
|
+
"",
|
|
98
|
+
].join("\n");
|
|
99
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { buildOperatorCapabilitiesDocument, operatorFailed, operatorSucceeded, processExitCodeForOutcome, writeOperatorJson, } from "../shared/operator/index.js";
|
|
2
|
+
import { buildOperatorGuideDocument, renderOperatorGuideMarkdown, } from "./operator-guide.js";
|
|
2
3
|
export function parseOperatorArgs(args) {
|
|
3
4
|
const options = {};
|
|
4
5
|
const positional = [];
|
|
@@ -7,19 +8,31 @@ export function parseOperatorArgs(args) {
|
|
|
7
8
|
options.json = true;
|
|
8
9
|
continue;
|
|
9
10
|
}
|
|
11
|
+
if (token === "--markdown") {
|
|
12
|
+
options.markdown = true;
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
10
15
|
if (token.startsWith("-")) {
|
|
11
|
-
throw new Error(`unknown flag ${token}\nusage: operator capabilities --json`);
|
|
16
|
+
throw new Error(`unknown flag ${token}\nusage: operator <capabilities|guide> [--json|--markdown]`);
|
|
12
17
|
}
|
|
13
18
|
positional.push(token);
|
|
14
19
|
}
|
|
15
20
|
const subcommand = positional[0];
|
|
16
21
|
if (!subcommand) {
|
|
17
|
-
throw new Error("usage: operator <capabilities> --json");
|
|
22
|
+
throw new Error("usage: operator <capabilities|guide> [--json|--markdown]");
|
|
18
23
|
}
|
|
19
24
|
return { subcommand, options };
|
|
20
25
|
}
|
|
21
|
-
export async function runOperator(
|
|
26
|
+
export async function runOperator(repoRoot, args) {
|
|
22
27
|
const { subcommand, options } = parseOperatorArgs(args);
|
|
28
|
+
if (subcommand === "guide") {
|
|
29
|
+
const guide = await buildOperatorGuideDocument(repoRoot);
|
|
30
|
+
if (options.markdown)
|
|
31
|
+
console.log(renderOperatorGuideMarkdown(guide));
|
|
32
|
+
else
|
|
33
|
+
emit(operatorSucceeded("operator guide", guide), true);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
23
36
|
if (subcommand !== "capabilities") {
|
|
24
37
|
const result = operatorFailed({
|
|
25
38
|
command: "operator",
|
|
@@ -38,7 +38,7 @@ import { isTargetTemplateTransientRetryNode, isWriterEmptyDiffRetryCandidate, is
|
|
|
38
38
|
import { assessBackendTestMdPlanCompleteness, assessBackendTestMdWriterCompleteness, assessBackendTestPytestPlanCompleteness, assessBackendTestPytestWriterCompleteness, assessBackendTestShardChildCompleteness, backendTestWriterProgressRoleForTask, classifyBackendTestWriterCompletenessFailure, isBackendTestCompletenessRetryCandidate, isBackendTestMdPlanTask, isBackendTestPytestCollectionRepairOutcomeRecoveryCandidate, isBackendTestPytestPlanTask, isBackendTestShardChildTask, writeBackendTestWriterProgressArtifacts, } from "../workflows/dag/backend-test-writer-completeness.js";
|
|
39
39
|
import { resolveBackendTestLayout } from "../workflows/dag/backend-test-layout.js";
|
|
40
40
|
import { assessBackendTestPlanProtocol } from "../workflows/dag/backend-test-plan-protocol.js";
|
|
41
|
-
import { frontendTestLayoutFromSpec } from "../workflows/dag/frontend-test-layout.js";
|
|
41
|
+
import { frontendTestContextRel, frontendTestLayoutFromSpec, } from "../workflows/dag/frontend-test-layout.js";
|
|
42
42
|
import { redactSecrets, truncateUtf8Preview } from "../shared/preview.js";
|
|
43
43
|
import { writeEffectiveContextReceipt } from "../workflows/dag/context-receipt.js";
|
|
44
44
|
import { estimateFrontendPlanRequirementRecordCalls, isWriterThinkingExhausted, readWriterThinkingExhaustionEvidence, WRITER_BUDGET_EXHAUSTED_CATEGORY, WRITER_TOKEN_BUDGET, FRONTEND_PLAN_COVERAGE_MAX_CONCURRENCY, mapWithConcurrency, buildFrontendPlanWorkload, loadOrCreateFrontendPlanCoverageLayout, collectFrontendVerificationCommandFiles, aggregateParallelPiResults, combineSequentialPiResults, runFrontendScoutParallelSessions, runFrontendContractSegmentedSessions, runFrontendPlanSegmentedSessions, runFrontendReviewSegmentedSessions, runFrontendScoutSegmentedSessions, } from "./dag-pi/sessions/index.js";
|
|
@@ -1552,7 +1552,7 @@ async function writePiExtensionsArtifact(nodeArtifactsDir, requested, resolved,
|
|
|
1552
1552
|
// best-effort run fact; node execution continues
|
|
1553
1553
|
}
|
|
1554
1554
|
}
|
|
1555
|
-
async function resolveValidatedFrontendBaseUrlFromContext(workspaceRoot, runDir) {
|
|
1555
|
+
async function resolveValidatedFrontendBaseUrlFromContext(workspaceRoot, runDir, layout) {
|
|
1556
1556
|
try {
|
|
1557
1557
|
const capability = JSON.parse(await readFile(path.join(runDir, "preflight-frontend-browser-tool-shell", "frontend-browser-capability.json"), "utf8"));
|
|
1558
1558
|
if (capability.status !== "ready" ||
|
|
@@ -1563,7 +1563,7 @@ async function resolveValidatedFrontendBaseUrlFromContext(workspaceRoot, runDir)
|
|
|
1563
1563
|
catch {
|
|
1564
1564
|
throw new Error("browser-command-capability-unavailable: missing or invalid frontend browser capability artifact");
|
|
1565
1565
|
}
|
|
1566
|
-
const contextPath = path.join(workspaceRoot, "
|
|
1566
|
+
const contextPath = path.join(workspaceRoot, ...frontendTestContextRel(layout).split("/"));
|
|
1567
1567
|
let markdown;
|
|
1568
1568
|
try {
|
|
1569
1569
|
markdown = await readFile(contextPath, "utf8");
|
|
@@ -1759,8 +1759,13 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
1759
1759
|
let beforeStatus;
|
|
1760
1760
|
let beforeWorkspaceSnapshot;
|
|
1761
1761
|
let beforePathFingerprints;
|
|
1762
|
-
/** tools-only and filesystem-only keep the tool sandbox but skip Git baseline.
|
|
1763
|
-
|
|
1762
|
+
/** tools-only and filesystem-only keep the tool sandbox but skip Git baseline.
|
|
1763
|
+
* filesystem-only applies when the node froze it in writeGuardPolicy (D4:
|
|
1764
|
+
* any dedicated test writer under an ignored root) OR the backend-test spec
|
|
1765
|
+
* explicitly selected workspaceControl=filesystem-only. */
|
|
1766
|
+
const filesystemOnly = isWriteTask &&
|
|
1767
|
+
(input.task.writeGuardPolicy === "filesystem-only" ||
|
|
1768
|
+
meta.spec.backendTestWorkspaceControl === "filesystem-only");
|
|
1764
1769
|
const skipGitWriteGuard = isWriteTask && (input.task.writeGuardPolicy === "tools-only" || filesystemOnly);
|
|
1765
1770
|
if (isWriteTask && filesystemOnly) {
|
|
1766
1771
|
try {
|
|
@@ -1880,7 +1885,7 @@ export async function executeDagPiNode(input, meta, piStepFn = executePiStep, wr
|
|
|
1880
1885
|
nodeId: input.task.id,
|
|
1881
1886
|
caseId,
|
|
1882
1887
|
evidenceDir,
|
|
1883
|
-
baseUrl: await resolveValidatedFrontendBaseUrlFromContext(input.cwd, meta.runDir),
|
|
1888
|
+
baseUrl: await resolveValidatedFrontendBaseUrlFromContext(input.cwd, meta.runDir, layout),
|
|
1884
1889
|
inputRoot: layout.fixturesDir,
|
|
1885
1890
|
layout,
|
|
1886
1891
|
};
|
|
@@ -8,7 +8,7 @@ import { computeExtensionToolAllowlistExtras } from "./pi-extension-resolver.js"
|
|
|
8
8
|
import { BoundedTextPreview, classifyPiFailure, createPiJsonlStreamCollector, DEFAULT_ABORT_GRACE_MS, DEFAULT_STALL_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, extractPiEventStopReason, extractAssistantTextFromPiJson, } from "./pi-executor.js";
|
|
9
9
|
import { serializeSessionEvent } from "./pi-event-serializer.js";
|
|
10
10
|
import { PI_RECOMMENDED_COMPACTION, PI_RECOMMENDED_RETRY, } from "../shared/pi-retry-settings.js";
|
|
11
|
-
import { createCompactionController, createContextPressureExtension, createSiftBridgeState, inMemoryCompactionSettings, isDagSiftEnabled, parseCompactionPolicy, parseSiftBridgeConfig, SIFT_RETRIEVE_TOOL_NAME, } from "../shared/pi-context-pressure/index.js";
|
|
11
|
+
import { createCompactionController, createContextPressureExtension, createSiftBridgeState, inMemoryCompactionSettings, isDagSiftEnabled, parseCompactionPolicy, parsePiToolResultPruneConfig, parseSiftBridgeConfig, SIFT_RETRIEVE_TOOL_NAME, } from "../shared/pi-context-pressure/index.js";
|
|
12
12
|
import { DAG_TELEMETRY_SESSION_ID_ENV, injectWizardLocalTelemetryHeaders, isTelemetrySessionId, } from "../shared/wizard-local-telemetry.js";
|
|
13
13
|
/** Maximum number of same-session continuations after provider output truncation. */
|
|
14
14
|
export const PI_SDK_OUTPUT_LIMIT_CONTINUE_MAX = 3;
|
|
@@ -292,6 +292,7 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
292
292
|
// Prefer SettingsManager.inMemory so harness runs do not depend on ~/.pi or
|
|
293
293
|
// project .pi/settings.json being present/trusted.
|
|
294
294
|
const policy = parseCompactionPolicy();
|
|
295
|
+
const pruneSettings = parsePiToolResultPruneConfig();
|
|
295
296
|
const compaction = {
|
|
296
297
|
...PI_RECOMMENDED_COMPACTION,
|
|
297
298
|
...inMemoryCompactionSettings(policy),
|
|
@@ -321,6 +322,7 @@ async function createSdkSession(sdk, input, shared) {
|
|
|
321
322
|
policy,
|
|
322
323
|
controller,
|
|
323
324
|
sift,
|
|
325
|
+
...(pruneSettings.enabled && policy.mode !== "off" ? { prune: pruneSettings.config } : {}),
|
|
324
326
|
});
|
|
325
327
|
const telemetryExtension = createDagWizardLocalTelemetryHeadersExtension(input.telemetrySessionId);
|
|
326
328
|
const extensionFactories = [pressureExtension, telemetryExtension].filter((extension) => Boolean(extension));
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { access as fsAccess, lstat, mkdir as fsMkdir, readFile as fsReadFile, realpath, writeFile as fsWriteFile, } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { pathMatchesPattern } from "../shared/git-progress.js";
|
|
4
|
+
import { isAnyTestArtifactPath, isHarnessBaselineForbiddenPattern, } from "../workflows/dag/test-artifact-namespace.js";
|
|
4
5
|
export class PiWriterPathDeniedError extends Error {
|
|
5
6
|
code;
|
|
6
7
|
targetPath;
|
|
@@ -105,12 +106,60 @@ function repoRelativePath(root, absoluteTarget, raw, allowRoot) {
|
|
|
105
106
|
}
|
|
106
107
|
return relative;
|
|
107
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* D3 test-artifact exception at the tool layer: a write target inside the
|
|
111
|
+
* canonical `.harness/testcase` namespace that the task's exclusive writeSet
|
|
112
|
+
* (or, for mkdir, a writeSet entry at/above it) explicitly owns is exempt
|
|
113
|
+
* from the BASELINE harness guard patterns (`.harness` / `.harness/**`) only.
|
|
114
|
+
* Explicit deeper forbidden patterns and the hard controller-facts guard stay
|
|
115
|
+
* authoritative, matching the DAG validator and the post-hoc write guard.
|
|
116
|
+
*/
|
|
117
|
+
function testArtifactWriteExempt(relative, ctx) {
|
|
118
|
+
const forbidden = ctx.forbiddenPaths ?? [];
|
|
119
|
+
let baselineHit = false;
|
|
120
|
+
for (const pattern of forbidden) {
|
|
121
|
+
if (!matchesAnyPattern(relative, [pattern]))
|
|
122
|
+
continue;
|
|
123
|
+
if (!isHarnessBaselineForbiddenPattern(pattern))
|
|
124
|
+
return false;
|
|
125
|
+
baselineHit = true;
|
|
126
|
+
}
|
|
127
|
+
if (!baselineHit)
|
|
128
|
+
return false;
|
|
129
|
+
if (!isAnyTestArtifactPath(relative))
|
|
130
|
+
return false;
|
|
131
|
+
const writeSet = ctx.writeSet ?? [];
|
|
132
|
+
if ((ctx.writePolicy ?? "exclusive") === "exclusive") {
|
|
133
|
+
return matchesAnyPattern(relative, writeSet);
|
|
134
|
+
}
|
|
135
|
+
return writeSet.length === 0 || matchesAnyPattern(relative, writeSet);
|
|
136
|
+
}
|
|
137
|
+
function testArtifactMkdirExempt(relative, ctx) {
|
|
138
|
+
const forbidden = ctx.forbiddenPaths ?? [];
|
|
139
|
+
let baselineHit = false;
|
|
140
|
+
for (const pattern of forbidden) {
|
|
141
|
+
if (!pathMatchesPattern(relative, pattern))
|
|
142
|
+
continue;
|
|
143
|
+
if (!isHarnessBaselineForbiddenPattern(pattern))
|
|
144
|
+
return false;
|
|
145
|
+
baselineHit = true;
|
|
146
|
+
}
|
|
147
|
+
if (!baselineHit)
|
|
148
|
+
return false;
|
|
149
|
+
if (!isAnyTestArtifactPath(relative))
|
|
150
|
+
return false;
|
|
151
|
+
const writeSet = (ctx.writeSet ?? []).map(normalizeRepoRelative);
|
|
152
|
+
return writeSet.some((candidate) => isPathAtOrBelowDirectory(candidate, relative) ||
|
|
153
|
+
(relative.length > 0 && pathMatchesPattern(relative, candidate)));
|
|
154
|
+
}
|
|
108
155
|
function assertWritableRelative(relative, ctx) {
|
|
109
156
|
if (isHardForbidden(relative)) {
|
|
110
157
|
throw new PiWriterPathDeniedError("hard-forbidden", relative, "writes under .harness/tasks/** or .harness/dag-runs/** are never allowed for Pi writers");
|
|
111
158
|
}
|
|
112
159
|
const forbidden = ctx.forbiddenPaths ?? [];
|
|
113
|
-
if (forbidden.length > 0 &&
|
|
160
|
+
if (forbidden.length > 0 &&
|
|
161
|
+
matchesAnyPattern(relative, forbidden) &&
|
|
162
|
+
!testArtifactWriteExempt(relative, ctx)) {
|
|
114
163
|
throw new PiWriterPathDeniedError("forbidden", relative);
|
|
115
164
|
}
|
|
116
165
|
const writeSet = ctx.writeSet ?? [];
|
|
@@ -130,7 +179,8 @@ function assertMkdirRelative(relative, ctx) {
|
|
|
130
179
|
const forbidden = ctx.forbiddenPaths ?? [];
|
|
131
180
|
if (relative &&
|
|
132
181
|
forbidden.length > 0 &&
|
|
133
|
-
matchesAnyPattern(relative, forbidden)
|
|
182
|
+
matchesAnyPattern(relative, forbidden) &&
|
|
183
|
+
!testArtifactMkdirExempt(relative, ctx)) {
|
|
134
184
|
throw new PiWriterPathDeniedError("forbidden", relative);
|
|
135
185
|
}
|
|
136
186
|
const writeSetCandidates = (ctx.writeSet ?? [])
|
|
@@ -149,8 +199,14 @@ function assertMkdirRelative(relative, ctx) {
|
|
|
149
199
|
if (nonHardForbiddenCandidates.length === 0) {
|
|
150
200
|
throw new PiWriterPathDeniedError("hard-forbidden", relative || ".");
|
|
151
201
|
}
|
|
152
|
-
|
|
153
|
-
|
|
202
|
+
// D3 test-artifact exception: when the target directory is a canonical
|
|
203
|
+
// namespace root owned by the writeSet, baseline harness guard patterns do
|
|
204
|
+
// not disqualify allowedPaths candidates either.
|
|
205
|
+
const effectiveForbidden = testArtifactMkdirExempt(relative, ctx)
|
|
206
|
+
? forbidden.filter((pattern) => !isHarnessBaselineForbiddenPattern(pattern))
|
|
207
|
+
: forbidden;
|
|
208
|
+
if (effectiveForbidden.length > 0 &&
|
|
209
|
+
nonHardForbiddenCandidates.every((candidate) => matchesAnyPattern(candidate, effectiveForbidden))) {
|
|
154
210
|
throw new PiWriterPathDeniedError("forbidden", relative || ".");
|
|
155
211
|
}
|
|
156
212
|
}
|
|
@@ -1,57 +1,43 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
const PACKAGE_NAME = "@playwright/cli";
|
|
5
|
+
const PACKAGE_VERSION = "0.1.20";
|
|
5
6
|
function launcherFromManifest(manifestPath) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (manifest.name !== PACKAGE_NAME)
|
|
9
|
-
return undefined;
|
|
10
|
-
const bin = typeof manifest.bin === "string"
|
|
11
|
-
? manifest.bin
|
|
12
|
-
: manifest.bin && typeof manifest.bin === "object"
|
|
13
|
-
? manifest.bin["playwright-cli"]
|
|
14
|
-
: undefined;
|
|
15
|
-
if (typeof bin !== "string" || !bin || path.isAbsolute(bin) || bin.split(/[\\/]+/).includes(".."))
|
|
16
|
-
return undefined;
|
|
17
|
-
const packageRoot = path.dirname(manifestPath);
|
|
18
|
-
const entryPath = path.resolve(packageRoot, bin);
|
|
19
|
-
const relative = path.relative(packageRoot, entryPath);
|
|
20
|
-
if (relative.startsWith("..") || path.isAbsolute(relative) || !existsSync(entryPath))
|
|
21
|
-
return undefined;
|
|
22
|
-
return { executable: process.execPath, argvPrefix: [entryPath], entryPath };
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
7
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
8
|
+
if (manifest.name !== PACKAGE_NAME)
|
|
25
9
|
return undefined;
|
|
10
|
+
if (manifest.version !== PACKAGE_VERSION) {
|
|
11
|
+
throw new Error(`expected ${PACKAGE_NAME}@${PACKAGE_VERSION}, found ${String(manifest.version)}`);
|
|
26
12
|
}
|
|
13
|
+
const bin = typeof manifest.bin === "string"
|
|
14
|
+
? manifest.bin
|
|
15
|
+
: manifest.bin && typeof manifest.bin === "object"
|
|
16
|
+
? manifest.bin["playwright-cli"]
|
|
17
|
+
: undefined;
|
|
18
|
+
if (typeof bin !== "string" || !bin || path.isAbsolute(bin) || path.win32.isAbsolute(bin) || bin.split(/[\\/]+/).includes(".."))
|
|
19
|
+
return undefined;
|
|
20
|
+
if (!/\.(?:c|m)?js$/i.test(bin))
|
|
21
|
+
return undefined;
|
|
22
|
+
const packageRoot = realpathSync(path.dirname(manifestPath));
|
|
23
|
+
const entryPath = realpathSync(path.resolve(packageRoot, bin));
|
|
24
|
+
const relative = path.relative(packageRoot, entryPath);
|
|
25
|
+
if (relative.startsWith("..") || path.isAbsolute(relative) || !statSync(entryPath).isFile())
|
|
26
|
+
return undefined;
|
|
27
|
+
return { executable: process.execPath, argvPrefix: [entryPath], entryPath };
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
paths.add(createRequire(path.join(path.resolve(root), "package.json")).resolve(`${PACKAGE_NAME}/package.json`));
|
|
33
|
-
}
|
|
34
|
-
catch {
|
|
35
|
-
// Keep searching controller-known module roots.
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
// Npm's Windows/global shim is paired with node_modules in either the shim
|
|
39
|
-
// directory or its parent (.bin). We read package metadata directly; the
|
|
40
|
-
// .cmd file itself is never executed or parsed as a command string.
|
|
41
|
-
for (const binDir of (process.env.PATH ?? "").split(path.delimiter).filter(Boolean)) {
|
|
42
|
-
paths.add(path.join(binDir, "node_modules", ...PACKAGE_NAME.split("/"), "package.json"));
|
|
43
|
-
paths.add(path.join(binDir, "..", ...PACKAGE_NAME.split("/"), "package.json"));
|
|
44
|
-
}
|
|
45
|
-
return [...paths];
|
|
46
|
-
}
|
|
47
|
-
/** Resolve only a package-declared entry; throws instead of falling back to a PATH executable. */
|
|
48
|
-
export function resolvePlaywrightCliLauncher(cwd) {
|
|
49
|
-
for (const manifestPath of candidateManifestPaths(cwd)) {
|
|
29
|
+
/** Resolve from this installation, independently of the target project's cwd/PATH. */
|
|
30
|
+
export function resolvePlaywrightCliLauncher(_cwd) {
|
|
31
|
+
try {
|
|
32
|
+
const manifestPath = createRequire(import.meta.url).resolve(`${PACKAGE_NAME}/package.json`);
|
|
50
33
|
const launcher = launcherFromManifest(manifestPath);
|
|
51
|
-
if (launcher)
|
|
52
|
-
|
|
34
|
+
if (!launcher)
|
|
35
|
+
throw new Error("invalid package manifest or JavaScript entry");
|
|
36
|
+
return launcher;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
throw new Error(`playwright-cli-unavailable: bundled ${PACKAGE_NAME} installation is missing or invalid; reinstall loop-agent. ${error instanceof Error ? error.message : String(error)}`);
|
|
53
40
|
}
|
|
54
|
-
throw new Error("playwright-cli-unavailable: verified @playwright/cli JavaScript entry was not found");
|
|
55
41
|
}
|
|
56
42
|
export function checkPlaywrightCliLauncher(cwd) {
|
|
57
43
|
try {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import { createHash } from "node:crypto";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { lstat, readlink, unlink } from "node:fs/promises";
|
|
5
4
|
import path from "node:path";
|
|
6
5
|
import { pathMatchesPattern } from "../shared/git-progress.js";
|
|
7
6
|
import { expandWorkspaceStatus, hasGitmodulesFile, synthesizeExpandedPorcelain, } from "../shared/git-workspace-status.js";
|
|
8
7
|
import { ResilientGitCommandError, runResilientGitCommand, } from "../shared/resilient-git.js";
|
|
8
|
+
import { isAnyTestArtifactPath, isHarnessBaselineForbiddenPattern, } from "../workflows/dag/test-artifact-namespace.js";
|
|
9
9
|
async function sha256File(filePath) {
|
|
10
10
|
const hash = createHash("sha256");
|
|
11
11
|
for await (const chunk of createReadStream(filePath)) {
|
|
@@ -149,7 +149,27 @@ export function validateShellWriteGuard(input) {
|
|
|
149
149
|
continue;
|
|
150
150
|
}
|
|
151
151
|
if (matchesAnyPattern(filePath, forbiddenPaths)) {
|
|
152
|
-
|
|
152
|
+
// D3 test-artifact exception: a changed file inside the canonical
|
|
153
|
+
// `.harness/testcase` namespace that the task's exclusive writeSet
|
|
154
|
+
// explicitly owns is exempt from the BASELINE harness guard patterns
|
|
155
|
+
// (`.harness` / `.harness/**`) only — spec-level validation gates such
|
|
156
|
+
// writeSets behind a frozen dedicated-test layout. Any explicit
|
|
157
|
+
// (deeper or out-of-tree) matching forbidden pattern still wins.
|
|
158
|
+
const baselinePatternHit = forbiddenPaths.some((forbidden) => isHarnessBaselineForbiddenPattern(forbidden) &&
|
|
159
|
+
pathMatchesPattern(filePath, forbidden));
|
|
160
|
+
const explicitPatternHit = forbiddenPaths.some((forbidden) => !isHarnessBaselineForbiddenPattern(forbidden) &&
|
|
161
|
+
pathMatchesPattern(filePath, forbidden));
|
|
162
|
+
const ownedByWriteSet = writePolicy === "exclusive" &&
|
|
163
|
+
writeSet.length > 0 &&
|
|
164
|
+
matchesAnyPattern(filePath, writeSet);
|
|
165
|
+
if (explicitPatternHit ||
|
|
166
|
+
!baselinePatternHit ||
|
|
167
|
+
!isAnyTestArtifactPath(filePath) ||
|
|
168
|
+
!ownedByWriteSet) {
|
|
169
|
+
violations.push(filePath);
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
// Exempted: canonical test-artifact file owned by the writeSet.
|
|
153
173
|
continue;
|
|
154
174
|
}
|
|
155
175
|
if (writePolicy === "read-only" || writePolicy === "none") {
|
|
@@ -380,37 +400,6 @@ export function deriveSameInstallationGitCandidates(primary) {
|
|
|
380
400
|
}
|
|
381
401
|
return Array.from(new Set([normalized, ...(fallback ? [fallback] : [])]));
|
|
382
402
|
}
|
|
383
|
-
async function resolveGitExecutableCandidates(platform, env) {
|
|
384
|
-
if (platform !== "win32")
|
|
385
|
-
return ["git"];
|
|
386
|
-
const pathValue = env.PATH ?? env.Path ?? "";
|
|
387
|
-
for (const rawEntry of pathValue.split(path.delimiter)) {
|
|
388
|
-
const entry = rawEntry.trim().replace(/^"|"$/g, "");
|
|
389
|
-
if (!entry)
|
|
390
|
-
continue;
|
|
391
|
-
const candidate = path.win32.join(entry, "git.exe");
|
|
392
|
-
try {
|
|
393
|
-
await access(candidate, constants.X_OK);
|
|
394
|
-
const sameInstallation = deriveSameInstallationGitCandidates(candidate);
|
|
395
|
-
const existing = [];
|
|
396
|
-
for (const executable of sameInstallation) {
|
|
397
|
-
try {
|
|
398
|
-
await access(executable, constants.X_OK);
|
|
399
|
-
existing.push(executable);
|
|
400
|
-
}
|
|
401
|
-
catch {
|
|
402
|
-
// Optional same-installation fallback is absent.
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
if (existing.length > 0)
|
|
406
|
-
return existing;
|
|
407
|
-
}
|
|
408
|
-
catch {
|
|
409
|
-
// Keep searching PATH entries.
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
return ["git"];
|
|
413
|
-
}
|
|
414
403
|
export async function readGitStatusPorcelain(cwd, options = {}) {
|
|
415
404
|
let parentPorcelain;
|
|
416
405
|
try {
|
|
@@ -510,38 +499,6 @@ export async function readGitStatusPorcelainWithDependencies(cwd, options, depen
|
|
|
510
499
|
attempts: attemptDiagnostics,
|
|
511
500
|
});
|
|
512
501
|
}
|
|
513
|
-
function readGitStatusPorcelainOnce(cwd, executable) {
|
|
514
|
-
return new Promise((resolve, reject) => {
|
|
515
|
-
const child = spawn(executable, ["status", "--porcelain=v1", "--untracked-files=all"], {
|
|
516
|
-
cwd,
|
|
517
|
-
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" },
|
|
518
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
519
|
-
windowsHide: true,
|
|
520
|
-
});
|
|
521
|
-
let stdout = "";
|
|
522
|
-
let stderr = "";
|
|
523
|
-
child.stdout.setEncoding("utf-8");
|
|
524
|
-
child.stderr.setEncoding("utf-8");
|
|
525
|
-
child.stdout.on("data", (chunk) => {
|
|
526
|
-
stdout += chunk;
|
|
527
|
-
});
|
|
528
|
-
child.stderr.on("data", (chunk) => {
|
|
529
|
-
stderr += chunk;
|
|
530
|
-
});
|
|
531
|
-
child.on("error", reject);
|
|
532
|
-
child.on("close", (code, signal) => {
|
|
533
|
-
if (code === 0) {
|
|
534
|
-
resolve(stdout);
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
const detail = stderr.trim() || stdout.trim() || "no stderr/stdout";
|
|
538
|
-
const error = new Error(`git status failed: ${detail}`);
|
|
539
|
-
error.exitCode = code;
|
|
540
|
-
error.signal = signal;
|
|
541
|
-
reject(error);
|
|
542
|
-
});
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
502
|
export async function runPostRunWriteGuard(input) {
|
|
546
503
|
const afterStatus = await readGitStatusPorcelain(input.rootCwd);
|
|
547
504
|
const beforeSnapshot = snapshotGitStatusPorcelain(input.beforeStatus);
|
|
@@ -191,12 +191,21 @@ function extractIndexReferences(indexContent) {
|
|
|
191
191
|
// `(${base})` and `` `base` ``. Bare mentions are intentionally NOT
|
|
192
192
|
// treated as references to avoid false positives from partial matches.
|
|
193
193
|
const linkMatches = [...line.matchAll(/\[[^\]]*\]\(([^)]+\.md)\)/g)];
|
|
194
|
+
// Link spans are blanked before the backtick scan: the index convention
|
|
195
|
+
// `[`archived.md`](../completed/archived.md)` puts a bare basename in the
|
|
196
|
+
// link TEXT, which is not an entry for this directory. Reading it as one
|
|
197
|
+
// reported every archived link as a stale active entry. Blanking (rather
|
|
198
|
+
// than filtering by content) keeps `[`local.md`](local.md)`, standalone
|
|
199
|
+
// `local.md` mentions, and mixed lines working, because those still
|
|
200
|
+
// contribute through their link target or an untouched backtick span.
|
|
201
|
+
let remainder = line;
|
|
194
202
|
for (const match of linkMatches) {
|
|
195
203
|
const reference = match[1] ? localIndexReference(match[1]) : null;
|
|
196
204
|
if (reference)
|
|
197
205
|
references.add(reference);
|
|
206
|
+
remainder = remainder.replace(match[0], " ".repeat(match[0].length));
|
|
198
207
|
}
|
|
199
|
-
const backtickMatches = [...
|
|
208
|
+
const backtickMatches = [...remainder.matchAll(/`([^`]+\.md)`/g)];
|
|
200
209
|
for (const match of backtickMatches) {
|
|
201
210
|
const reference = match[1] ? localIndexReference(match[1]) : null;
|
|
202
211
|
if (reference)
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { harnessManifestSchema } from '../shared/types.js';
|
|
1
|
+
import { loadNormalizedProjectConfig } from './project-config.js';
|
|
4
2
|
export async function loadHarnessManifest(repoRoot) {
|
|
5
|
-
|
|
6
|
-
const raw = await readFile(manifestPath, 'utf-8');
|
|
7
|
-
const parsed = JSON.parse(raw);
|
|
8
|
-
return harnessManifestSchema.parse(parsed);
|
|
3
|
+
return (await loadNormalizedProjectConfig(repoRoot)).manifest;
|
|
9
4
|
}
|
|
@@ -209,14 +209,18 @@ export const nightSchedulerPolicySchema = z.object({
|
|
|
209
209
|
export const workerPolicySchema = z.object({
|
|
210
210
|
nightScheduler: nightSchedulerPolicySchema.optional().default({}),
|
|
211
211
|
});
|
|
212
|
-
export const
|
|
213
|
-
export const harnessManifestSchema = z
|
|
212
|
+
export const verificationCommandsSchema = z
|
|
214
213
|
.object({
|
|
214
|
+
quick: z.array(z.string().trim().min(1)).optional().default([]),
|
|
215
|
+
standard: z.array(z.string().trim().min(1)).optional().default([]),
|
|
216
|
+
full: z.array(z.string().trim().min(1)).optional().default([]),
|
|
217
|
+
})
|
|
218
|
+
.strict();
|
|
219
|
+
export const CURSOR_HARNESS_EXECUTOR_REMOVED_ERROR = "harness executors.cursor is no longer supported; remove it and use executors.pi only (Cursor is available only via cursor-prompt sidecar)";
|
|
220
|
+
const harnessManifestCommonShape = {
|
|
215
221
|
$schema: z.string().optional(),
|
|
216
|
-
version: z.number(),
|
|
217
222
|
project: z.string(),
|
|
218
223
|
adapter: z.string().optional(),
|
|
219
|
-
governanceRoot: z.string(),
|
|
220
224
|
features: z
|
|
221
225
|
.object({
|
|
222
226
|
goals: z.boolean().optional().default(true),
|
|
@@ -225,9 +229,7 @@ export const harnessManifestSchema = z
|
|
|
225
229
|
.default({
|
|
226
230
|
goals: true,
|
|
227
231
|
}),
|
|
228
|
-
|
|
229
|
-
artifacts: z.record(z.string(), z.string().nullable()),
|
|
230
|
-
scripts: z.record(z.string(), z.string().nullable()),
|
|
232
|
+
verification: verificationCommandsSchema.optional().default({}),
|
|
231
233
|
workflowPolicy: workflowPolicySchema,
|
|
232
234
|
/** Optional; absent means Night Scheduler uses package defaults when enabled by callers. */
|
|
233
235
|
workerPolicy: workerPolicySchema.optional(),
|
|
@@ -237,8 +239,25 @@ export const harnessManifestSchema = z
|
|
|
237
239
|
.strict()
|
|
238
240
|
.optional()
|
|
239
241
|
.default({}),
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
+
};
|
|
243
|
+
const harnessManifestV1Schema = z.object({
|
|
244
|
+
...harnessManifestCommonShape,
|
|
245
|
+
version: z.literal(1),
|
|
246
|
+
governanceRoot: z.string(),
|
|
247
|
+
entrypoints: z.record(z.string(), z.string().nullable()),
|
|
248
|
+
artifacts: z.record(z.string(), z.string().nullable()),
|
|
249
|
+
scripts: z.record(z.string(), z.string().nullable()),
|
|
250
|
+
}).strict();
|
|
251
|
+
const harnessManifestV2Schema = z.object({
|
|
252
|
+
...harnessManifestCommonShape,
|
|
253
|
+
version: z.literal(2),
|
|
254
|
+
governanceRoot: z.string().optional().default("docs"),
|
|
255
|
+
entrypoints: z.record(z.string(), z.string().nullable()).optional().default({}),
|
|
256
|
+
artifacts: z.record(z.string(), z.string().nullable()).optional().default({}),
|
|
257
|
+
scripts: z.record(z.string(), z.string().nullable()).optional().default({}),
|
|
258
|
+
}).strict();
|
|
259
|
+
export const harnessManifestSchema = z
|
|
260
|
+
.discriminatedUnion("version", [harnessManifestV1Schema, harnessManifestV2Schema])
|
|
242
261
|
.superRefine((manifest, ctx) => {
|
|
243
262
|
// Preserve the empty read-only compatibility projection for callers that
|
|
244
263
|
// inspect parsed manifests, while rejecting this field in JSON input.
|