zob-harness 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/adapters/registry.json +103 -0
- package/.pi/agents/architecture-cartographer.md +53 -0
- package/.pi/agents/chief-vision.md +39 -0
- package/.pi/agents/clarifier.md +58 -0
- package/.pi/agents/context-steward.md +52 -0
- package/.pi/agents/doc-steward.md +34 -0
- package/.pi/agents/explore.md +49 -0
- package/.pi/agents/factory.md +41 -0
- package/.pi/agents/implementer.md +44 -0
- package/.pi/agents/librarian.md +32 -0
- package/.pi/agents/oracle-merge.md +50 -0
- package/.pi/agents/oracle.md +55 -0
- package/.pi/agents/pattern-miner.md +53 -0
- package/.pi/agents/planner.md +39 -0
- package/.pi/agents/project-dna-golden-evaluator.md +32 -0
- package/.pi/agents/project-dna-ontology-steward.md +30 -0
- package/.pi/agents/project-dna-oracle.md +56 -0
- package/.pi/agents/project-dna-orchestrator.md +60 -0
- package/.pi/agents/project-dna-query-steward.md +38 -0
- package/.pi/agents/project-dna-safety-preflight.md +54 -0
- package/.pi/agents/project-dna-test-linker.md +27 -0
- package/.pi/agents/qa.md +38 -0
- package/.pi/agents/refactor-cartographer.md +28 -0
- package/.pi/agents/refactor-mover.md +31 -0
- package/.pi/agents/refactor-oracle.md +49 -0
- package/.pi/agents/repo-scout.md +60 -0
- package/.pi/agents/sample-architect.md +48 -0
- package/.pi/agents/specifier.md +57 -0
- package/.pi/agents/symbol-range-curator.md +41 -0
- package/.pi/agents/synthesis.md +52 -0
- package/.pi/agents/temp-agent-creator.md +35 -0
- package/.pi/autonomy-policy.json +67 -0
- package/.pi/budget-policy.json +54 -0
- package/.pi/capabilities/zob-public-runtime-capabilities.json +1700 -0
- package/.pi/chains/explore-plan-oracle.json +78 -0
- package/.pi/chains/explore-spec-clarify-plan-oracle.json +64 -0
- package/.pi/chains/explore-spec-plan-oracle.json +53 -0
- package/.pi/chains/spec-clarify-plan-oracle.json +53 -0
- package/.pi/chains/spec-factory-oracle.json +42 -0
- package/.pi/chains/spec-plan-oracle.json +42 -0
- package/.pi/compute-profiles/defaults.json +19 -0
- package/.pi/compute-profiles/overrides.json +13 -0
- package/.pi/compute-profiles/risk-rules.json +16 -0
- package/.pi/daemon-policy.json +80 -0
- package/.pi/damage-control-rules.json +45 -0
- package/.pi/extensions/zob-child-safety/index.ts +212 -0
- package/.pi/extensions/zob-harness/AGENTS.md +28 -0
- package/.pi/extensions/zob-harness/index.ts +391 -0
- package/.pi/extensions/zob-harness/src/AGENTS.md +25 -0
- package/.pi/extensions/zob-harness/src/agents.ts +82 -0
- package/.pi/extensions/zob-harness/src/autonomous-runtime.ts +2912 -0
- package/.pi/extensions/zob-harness/src/autonomy-readiness.ts +778 -0
- package/.pi/extensions/zob-harness/src/budget-policy.ts +308 -0
- package/.pi/extensions/zob-harness/src/capabilities.ts +249 -0
- package/.pi/extensions/zob-harness/src/child-runner.ts +249 -0
- package/.pi/extensions/zob-harness/src/chronicle.ts +262 -0
- package/.pi/extensions/zob-harness/src/compute-profile.ts +602 -0
- package/.pi/extensions/zob-harness/src/compute-workflow-shape.ts +168 -0
- package/.pi/extensions/zob-harness/src/coms-v2/AGENTS.md +16 -0
- package/.pi/extensions/zob-harness/src/coms-v2/envelope.ts +121 -0
- package/.pi/extensions/zob-harness/src/coms-v2/identity.ts +53 -0
- package/.pi/extensions/zob-harness/src/coms-v2/ledger-bridge.ts +67 -0
- package/.pi/extensions/zob-harness/src/coms-v2/local-transport.ts +147 -0
- package/.pi/extensions/zob-harness/src/coms-v2/pending-replies.ts +80 -0
- package/.pi/extensions/zob-harness/src/coms-v2/policy.ts +125 -0
- package/.pi/extensions/zob-harness/src/coms-v2/presence.ts +55 -0
- package/.pi/extensions/zob-harness/src/coms-v2/registry.ts +113 -0
- package/.pi/extensions/zob-harness/src/coms-v2/response-capture.ts +50 -0
- package/.pi/extensions/zob-harness/src/coms-v2/transcript-capture.ts +164 -0
- package/.pi/extensions/zob-harness/src/coms-v2/types.ts +149 -0
- package/.pi/extensions/zob-harness/src/coms-v2/zpeer-profile.ts +140 -0
- package/.pi/extensions/zob-harness/src/coms-v2/zpeer.ts +452 -0
- package/.pi/extensions/zob-harness/src/constants.ts +108 -0
- package/.pi/extensions/zob-harness/src/context-gbrain.ts +465 -0
- package/.pi/extensions/zob-harness/src/daemon-policy.ts +223 -0
- package/.pi/extensions/zob-harness/src/daemon-readiness.ts +134 -0
- package/.pi/extensions/zob-harness/src/daemon-runtime.ts +393 -0
- package/.pi/extensions/zob-harness/src/factory/AGENTS.md +24 -0
- package/.pi/extensions/zob-harness/src/factory/agentic-plan.ts +65 -0
- package/.pi/extensions/zob-harness/src/factory/quarantine.ts +319 -0
- package/.pi/extensions/zob-harness/src/factory/run.ts +520 -0
- package/.pi/extensions/zob-harness/src/factory/validation.ts +454 -0
- package/.pi/extensions/zob-harness/src/factory-selector.ts +318 -0
- package/.pi/extensions/zob-harness/src/full-autonomy-test.ts +226 -0
- package/.pi/extensions/zob-harness/src/git-ops.ts +868 -0
- package/.pi/extensions/zob-harness/src/goal-room.ts +178 -0
- package/.pi/extensions/zob-harness/src/goal-runtime.ts +1569 -0
- package/.pi/extensions/zob-harness/src/goal-todo-imports.ts +111 -0
- package/.pi/extensions/zob-harness/src/goal-todo-types.ts +231 -0
- package/.pi/extensions/zob-harness/src/goal-todos.ts +1410 -0
- package/.pi/extensions/zob-harness/src/goal.ts +152 -0
- package/.pi/extensions/zob-harness/src/governed-requests.ts +436 -0
- package/.pi/extensions/zob-harness/src/interactive-autonomy.ts +595 -0
- package/.pi/extensions/zob-harness/src/launch-apply.ts +313 -0
- package/.pi/extensions/zob-harness/src/merge-queue.ts +290 -0
- package/.pi/extensions/zob-harness/src/mission-control.ts +573 -0
- package/.pi/extensions/zob-harness/src/model-availability.ts +52 -0
- package/.pi/extensions/zob-harness/src/model-routing.ts +429 -0
- package/.pi/extensions/zob-harness/src/orchestration/AGENTS.md +23 -0
- package/.pi/extensions/zob-harness/src/orchestration/adaptive-delegation.ts +547 -0
- package/.pi/extensions/zob-harness/src/orchestration/adaptive-workflow.ts +585 -0
- package/.pi/extensions/zob-harness/src/orchestration/lead-plan.ts +192 -0
- package/.pi/extensions/zob-harness/src/orchestration/plan.ts +168 -0
- package/.pi/extensions/zob-harness/src/orchestration/room.ts +346 -0
- package/.pi/extensions/zob-harness/src/orchestration/run.ts +134 -0
- package/.pi/extensions/zob-harness/src/orchestration/supervised-readonly.ts +1147 -0
- package/.pi/extensions/zob-harness/src/orchestration/widget-readers.ts +132 -0
- package/.pi/extensions/zob-harness/src/output-contracts.ts +656 -0
- package/.pi/extensions/zob-harness/src/project-dna.ts +533 -0
- package/.pi/extensions/zob-harness/src/promotion/AGENTS.md +24 -0
- package/.pi/extensions/zob-harness/src/promotion/candidate.ts +336 -0
- package/.pi/extensions/zob-harness/src/promotion/coms.ts +127 -0
- package/.pi/extensions/zob-harness/src/promotion/documentation.ts +142 -0
- package/.pi/extensions/zob-harness/src/promotion/factory.ts +107 -0
- package/.pi/extensions/zob-harness/src/promotion/ledger.ts +2 -0
- package/.pi/extensions/zob-harness/src/promotion/temp-agent.ts +151 -0
- package/.pi/extensions/zob-harness/src/promotion/types.ts +149 -0
- package/.pi/extensions/zob-harness/src/promotion/validate.ts +6 -0
- package/.pi/extensions/zob-harness/src/promotion/write-lane.ts +162 -0
- package/.pi/extensions/zob-harness/src/prompt-packs.ts +239 -0
- package/.pi/extensions/zob-harness/src/queue.ts +386 -0
- package/.pi/extensions/zob-harness/src/rules.ts +225 -0
- package/.pi/extensions/zob-harness/src/runtime/AGENTS.md +26 -0
- package/.pi/extensions/zob-harness/src/runtime/adaptive-zmode.ts +116 -0
- package/.pi/extensions/zob-harness/src/runtime/auto-compaction.ts +715 -0
- package/.pi/extensions/zob-harness/src/runtime/commands.ts +1315 -0
- package/.pi/extensions/zob-harness/src/runtime/compaction-policy.ts +516 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-click-markers.ts +141 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-feed.ts +415 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-markdown.ts +97 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-monitor.ts +553 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-mouse.ts +205 -0
- package/.pi/extensions/zob-harness/src/runtime/delegation-overlay.ts +434 -0
- package/.pi/extensions/zob-harness/src/runtime/events.ts +736 -0
- package/.pi/extensions/zob-harness/src/runtime/goal-todo-overlay.ts +214 -0
- package/.pi/extensions/zob-harness/src/runtime/mode-intent.ts +144 -0
- package/.pi/extensions/zob-harness/src/runtime/plan-capture.ts +270 -0
- package/.pi/extensions/zob-harness/src/runtime/state.ts +403 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-autonomous.ts +117 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-compute.ts +136 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-coms.ts +365 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-context.ts +70 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +1854 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-factory.ts +810 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-goal-room.ts +46 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-governed-requests.ts +38 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-merge-queue.ts +61 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-mission-control.ts +77 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-orchestration.ts +106 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-project-dna.ts +123 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-worker-pool.ts +93 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-workspace-claims.ts +62 -0
- package/.pi/extensions/zob-harness/src/runtime/tools-zcommit.ts +147 -0
- package/.pi/extensions/zob-harness/src/runtime/widget.ts +353 -0
- package/.pi/extensions/zob-harness/src/runtime/zobHarness.ts +60 -0
- package/.pi/extensions/zob-harness/src/safety.ts +338 -0
- package/.pi/extensions/zob-harness/src/sandbox.ts +1508 -0
- package/.pi/extensions/zob-harness/src/schemas-project-dna.ts +47 -0
- package/.pi/extensions/zob-harness/src/schemas.ts +695 -0
- package/.pi/extensions/zob-harness/src/telemetry.ts +373 -0
- package/.pi/extensions/zob-harness/src/topology/AGENTS.md +22 -0
- package/.pi/extensions/zob-harness/src/topology/chains.ts +236 -0
- package/.pi/extensions/zob-harness/src/topology/coms.ts +211 -0
- package/.pi/extensions/zob-harness/src/topology/orchestration-profiles.ts +204 -0
- package/.pi/extensions/zob-harness/src/topology/teams.ts +113 -0
- package/.pi/extensions/zob-harness/src/types/core.ts +47 -0
- package/.pi/extensions/zob-harness/src/types.ts +939 -0
- package/.pi/extensions/zob-harness/src/utils/AGENTS.md +22 -0
- package/.pi/extensions/zob-harness/src/utils/formatting.ts +34 -0
- package/.pi/extensions/zob-harness/src/utils/hashing.ts +11 -0
- package/.pi/extensions/zob-harness/src/utils/json.ts +28 -0
- package/.pi/extensions/zob-harness/src/utils/paths.ts +54 -0
- package/.pi/extensions/zob-harness/src/utils/records.ts +25 -0
- package/.pi/extensions/zob-harness/src/utils/resources.ts +38 -0
- package/.pi/extensions/zob-harness/src/worker-pool.ts +672 -0
- package/.pi/extensions/zob-harness/src/workspace-claims.ts +297 -0
- package/.pi/extensions/zob-switch/index.ts +180 -0
- package/.pi/factories/budget-preflight-dry-run/batch-manifest.json +59 -0
- package/.pi/factories/budget-preflight-dry-run/factory.json +94 -0
- package/.pi/factories/budget-preflight-dry-run/pilot-manifest.json +50 -0
- package/.pi/factories/budget-preflight-dry-run/smoke-manifest.json +43 -0
- package/.pi/factories/code-review-matrix/batch-manifest.json +61 -0
- package/.pi/factories/code-review-matrix/factory.json +163 -0
- package/.pi/factories/code-review-matrix/pilot-manifest.json +41 -0
- package/.pi/factories/code-review-matrix/smoke-manifest.json +35 -0
- package/.pi/factories/factory-forge/batch-manifest.json +56 -0
- package/.pi/factories/factory-forge/factory.json +84 -0
- package/.pi/factories/factory-forge/pilot-manifest.json +32 -0
- package/.pi/factories/factory-forge/smoke-manifest.json +19 -0
- package/.pi/factories/opencode-pattern-canonizer/batch-manifest.json +54 -0
- package/.pi/factories/opencode-pattern-canonizer/factory.json +86 -0
- package/.pi/factories/opencode-pattern-canonizer/pilot-manifest.json +39 -0
- package/.pi/factories/opencode-pattern-canonizer/smoke-manifest.json +26 -0
- package/.pi/factories/project-dna/README.md +182 -0
- package/.pi/factories/project-dna/batch-manifest.json +37 -0
- package/.pi/factories/project-dna/example-project-dna-manifest-v2.json +80 -0
- package/.pi/factories/project-dna/example-project-dna-manifest.json +58 -0
- package/.pi/factories/project-dna/factory.json +131 -0
- package/.pi/factories/project-dna/golden-cases-smoke.json +62 -0
- package/.pi/factories/project-dna/pi-agentic-ontology.json +88 -0
- package/.pi/factories/project-dna/pilot-manifest.json +32 -0
- package/.pi/factories/project-dna/schemas/benchmark-suite.schema.json +27 -0
- package/.pi/factories/project-dna/schemas/code-knowledge-graph.schema.json +97 -0
- package/.pi/factories/project-dna/schemas/context-pack.schema.json +43 -0
- package/.pi/factories/project-dna/schemas/golden-case.schema.json +36 -0
- package/.pi/factories/project-dna/schemas/manifest-v2.schema.json +128 -0
- package/.pi/factories/project-dna/schemas/manifest.schema.json +77 -0
- package/.pi/factories/project-dna/schemas/ontology.schema.json +45 -0
- package/.pi/factories/project-dna/schemas/project-fingerprint.schema.json +28 -0
- package/.pi/factories/project-dna/schemas/query-steward-report.schema.json +52 -0
- package/.pi/factories/project-dna/smoke-manifest.json +27 -0
- package/.pi/factories/roadmap-smoke-lots/batch-manifest.json +49 -0
- package/.pi/factories/roadmap-smoke-lots/factory.json +89 -0
- package/.pi/factories/roadmap-smoke-lots/pilot-manifest.json +50 -0
- package/.pi/factories/roadmap-smoke-lots/smoke-manifest.json +35 -0
- package/.pi/git-policy.json +120 -0
- package/.pi/mission-control/zob_coms_transport.json +64 -0
- package/.pi/model-catalog.example.json +345 -0
- package/.pi/model-economy.example.json +196 -0
- package/.pi/model-routing.json +86 -0
- package/.pi/orchestrations/adaptive-chief-vision.json +193 -0
- package/.pi/orchestrations/ceo-feature-build.json +182 -0
- package/.pi/orchestrations/readonly-dynamic-smoke.json +75 -0
- package/.pi/output-contracts/agent-event.v1.json +19 -0
- package/.pi/output-contracts/base.v1.json +24 -0
- package/.pi/output-contracts/brain-lookup.v1.json +21 -0
- package/.pi/output-contracts/clarification.v1.json +21 -0
- package/.pi/output-contracts/context-pack.v1.json +20 -0
- package/.pi/output-contracts/context-request.v1.json +21 -0
- package/.pi/output-contracts/context-steward.v1.json +19 -0
- package/.pi/output-contracts/context-writeback-proposal.v1.json +18 -0
- package/.pi/output-contracts/delegation-request.v1.json +21 -0
- package/.pi/output-contracts/explore.v1.json +52 -0
- package/.pi/output-contracts/factory.v1.json +48 -0
- package/.pi/output-contracts/guidance-steward.v1.json +18 -0
- package/.pi/output-contracts/implement.v1.json +40 -0
- package/.pi/output-contracts/launch-authorization.v1.json +21 -0
- package/.pi/output-contracts/lead-plan.v1.json +22 -0
- package/.pi/output-contracts/mission-readiness.v1.json +20 -0
- package/.pi/output-contracts/oracle-merge.v1.json +44 -0
- package/.pi/output-contracts/oracle-request.v1.json +20 -0
- package/.pi/output-contracts/oracle.v1.json +44 -0
- package/.pi/output-contracts/orchestration-profile.v1.json +22 -0
- package/.pi/output-contracts/plan.v1.json +48 -0
- package/.pi/output-contracts/prompt-pack.v1.json +20 -0
- package/.pi/output-contracts/qa.v1.json +40 -0
- package/.pi/output-contracts/research.v1.json +36 -0
- package/.pi/output-contracts/spec.v1.json +22 -0
- package/.pi/output-contracts/synthesis.v1.json +44 -0
- package/.pi/output-contracts/temp-agent-card.v1.json +23 -0
- package/.pi/output-contracts/todo-child-result.v1.json +20 -0
- package/.pi/output-contracts/todo-child-result.v2.json +22 -0
- package/.pi/output-contracts/todo-claim-validation.v1.json +22 -0
- package/.pi/output-contracts/todo-split-request.v1.json +20 -0
- package/.pi/prompts/adaptive-workflow.md +63 -0
- package/.pi/prompts/autonomous-runtime.md +15 -0
- package/.pi/prompts/benchmark-contender.md +15 -0
- package/.pi/prompts/benchmark-judge.md +19 -0
- package/.pi/prompts/clarify-spec.md +20 -0
- package/.pi/prompts/compute-plan.md +36 -0
- package/.pi/prompts/compute-preview.md +42 -0
- package/.pi/prompts/contract.md +29 -0
- package/.pi/prompts/explore.md +13 -0
- package/.pi/prompts/factory-run.md +36 -0
- package/.pi/prompts/factory.md +20 -0
- package/.pi/prompts/implement.md +27 -0
- package/.pi/prompts/model-catalog.md +68 -0
- package/.pi/prompts/model-economy.md +64 -0
- package/.pi/prompts/oracle-merge.md +18 -0
- package/.pi/prompts/oracle.md +13 -0
- package/.pi/prompts/orchestrator.md +48 -0
- package/.pi/prompts/parallel-review.md +21 -0
- package/.pi/prompts/plan.md +21 -0
- package/.pi/prompts/project-dna.md +90 -0
- package/.pi/prompts/refactor-oracle.md +23 -0
- package/.pi/prompts/refactor-slice.md +24 -0
- package/.pi/prompts/research.md +20 -0
- package/.pi/prompts/spec.md +19 -0
- package/.pi/prompts/synthesis.md +18 -0
- package/.pi/rules/always.md +38 -0
- package/.pi/rules/docs.md +32 -0
- package/.pi/rules/factory.md +44 -0
- package/.pi/rules/oracle.md +34 -0
- package/.pi/rules/orchestration.md +44 -0
- package/.pi/rules/project.md +34 -0
- package/.pi/rules/prompts.md +43 -0
- package/.pi/rules/runtime.md +43 -0
- package/.pi/rules/sandbox.md +43 -0
- package/.pi/settings.json +28 -0
- package/.pi/skills/zob-agentic-access/SKILL.md +20 -0
- package/.pi/skills/zob-autonomous-runtime/SKILL.md +41 -0
- package/.pi/skills/zob-commit/SKILL.md +79 -0
- package/.pi/skills/zob-compaction-policy/SKILL.md +92 -0
- package/.pi/skills/zob-compute-profile/SKILL.md +108 -0
- package/.pi/skills/zob-coms-safety/SKILL.md +54 -0
- package/.pi/skills/zob-coms-v2-live/SKILL.md +47 -0
- package/.pi/skills/zob-delegation-routing/SKILL.md +82 -0
- package/.pi/skills/zob-factory/SKILL.md +28 -0
- package/.pi/skills/zob-goal-todo-tree/SKILL.md +279 -0
- package/.pi/skills/zob-harness/SKILL.md +68 -0
- package/.pi/skills/zob-mission-control-coms/SKILL.md +39 -0
- package/.pi/skills/zob-oracle/SKILL.md +21 -0
- package/.pi/skills/zob-owner-pool-drill-writer/SKILL.md +244 -0
- package/.pi/skills/zob-owner-pool-launcher/SKILL.md +261 -0
- package/.pi/skills/zob-project-dna/SKILL.md +275 -0
- package/.pi/skills/zob-sandbox/SKILL.md +29 -0
- package/.pi/skills/zob-spec/SKILL.md +25 -0
- package/.pi/skills/zob-split-refactor/SKILL.md +39 -0
- package/.pi/skills/zob-tool-router/SKILL.md +104 -0
- package/.pi/teams/zob-core.json +122 -0
- package/AGENTS.md +89 -0
- package/CONTRIBUTING.md +56 -0
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/SECURITY.md +35 -0
- package/SOURCE_INDEX.md +46 -0
- package/package.json +135 -0
- package/scripts/README.md +57 -0
- package/scripts/autonomy/mission-readiness-secret-smoke.mjs +90 -0
- package/scripts/compute-profile/plan-workflow.mjs +85 -0
- package/scripts/compute-profile/preview.mjs +242 -0
- package/scripts/compute-profile/regression-smoke.mjs +38 -0
- package/scripts/compute-profile/summarize.mjs +72 -0
- package/scripts/compute-profile/validate-policy.mjs +50 -0
- package/scripts/compute-profile/validate-preview.mjs +95 -0
- package/scripts/compute-profile/validate-workflow.mjs +58 -0
- package/scripts/git-ops/commit-policy-smoke.mjs +221 -0
- package/scripts/goal-todo/child-goal-ref-smoke.mjs +252 -0
- package/scripts/harness-switch/static-smoke.mjs +43 -0
- package/scripts/model-catalog/validate-economy.mjs +223 -0
- package/scripts/model-catalog/validate.mjs +199 -0
- package/scripts/package-surface/validate-script-refs.mjs +190 -0
- package/scripts/path-policy/validate-smoke.mjs +103 -0
- package/scripts/project-dna/bench-smoke.mjs +217 -0
- package/scripts/project-dna/build-capsules.mjs +207 -0
- package/scripts/project-dna/build-sample-spec.mjs +140 -0
- package/scripts/project-dna/emit-golden-cases.mjs +75 -0
- package/scripts/project-dna/emit-ontology.mjs +75 -0
- package/scripts/project-dna/generate-sample.mjs +302 -0
- package/scripts/project-dna/oracle-review-smoke.mjs +157 -0
- package/scripts/project-dna/plan-workflow.mjs +289 -0
- package/scripts/project-dna/query-context.mjs +276 -0
- package/scripts/project-dna/query-steward.mjs +149 -0
- package/scripts/project-dna/scan.mjs +553 -0
- package/scripts/project-dna/validate-5of5.mjs +159 -0
- package/scripts/project-dna/validate-golden-cases.mjs +78 -0
- package/scripts/project-dna/validate-ontology.mjs +97 -0
- package/scripts/project-dna/validate-sample-project.mjs +105 -0
- package/scripts/project-dna/validate-scaffold.mjs +383 -0
- package/scripts/project-dna/validate-scan-artifacts.mjs +187 -0
- package/scripts/project-dna/validate-workflow.mjs +166 -0
- package/scripts/start-pi.sh +4 -0
- package/scripts/worker-pool/static-smoke.mjs +54 -0
- package/scripts/zpeer-local-e2e-smoke.mjs +395 -0
- package/scripts/zpeer-static-smoke.mjs +129 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { matchesKey, truncateToWidth, visibleWidth, type Component } from "@earendil-works/pi-tui";
|
|
3
|
+
|
|
4
|
+
import { formatGoalTodoSummary, summarizeGoalTodos, type GoalTodoNode } from "../goal-todos.js";
|
|
5
|
+
import type { HarnessRuntimeState } from "./state.js";
|
|
6
|
+
|
|
7
|
+
function padToWidth(text: string, width: number): string {
|
|
8
|
+
const clipped = truncateToWidth(text, Math.max(1, width), "…");
|
|
9
|
+
return clipped + " ".repeat(Math.max(0, width - visibleWidth(clipped)));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function statusIcon(status: GoalTodoNode["status"]): string {
|
|
13
|
+
if (status === "done") return "✓";
|
|
14
|
+
if (status === "skipped") return "↷";
|
|
15
|
+
if (status === "blocked") return "▲";
|
|
16
|
+
if (status === "delegated") return "⇄";
|
|
17
|
+
if (status === "claim_returned") return "◇";
|
|
18
|
+
if (status === "needs_oracle") return "◆";
|
|
19
|
+
if (status === "needs_user") return "?";
|
|
20
|
+
if (status === "in_progress") return "●";
|
|
21
|
+
return "○";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function statusColor(status: GoalTodoNode["status"]): "success" | "warning" | "error" | "muted" {
|
|
25
|
+
if (status === "done" || status === "skipped") return "success";
|
|
26
|
+
if (status === "blocked" || status === "needs_user") return "error";
|
|
27
|
+
if (status === "delegated" || status === "claim_returned" || status === "needs_oracle" || status === "in_progress") return "warning";
|
|
28
|
+
return "muted";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function goalNodes(state: HarnessRuntimeState): GoalTodoNode[] {
|
|
32
|
+
const goalId = state.runtimeGoal?.goalId;
|
|
33
|
+
if (!goalId) return [];
|
|
34
|
+
return state.goalTodos.nodes
|
|
35
|
+
.filter((node) => node.goalId === goalId)
|
|
36
|
+
.sort((left, right) => left.path.localeCompare(right.path, undefined, { numeric: true }) || left.createdAt - right.createdAt);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class GoalTodoOverlayComponent implements Component {
|
|
40
|
+
private selectedTodoId?: string;
|
|
41
|
+
private listScroll = 0;
|
|
42
|
+
private detailScroll = 0;
|
|
43
|
+
private filter = "";
|
|
44
|
+
private filterEditing = false;
|
|
45
|
+
private readonly height = 22;
|
|
46
|
+
|
|
47
|
+
constructor(
|
|
48
|
+
private readonly runtimeState: HarnessRuntimeState,
|
|
49
|
+
private readonly theme: Theme,
|
|
50
|
+
private readonly done: () => void,
|
|
51
|
+
initialTodoId?: string,
|
|
52
|
+
) {
|
|
53
|
+
this.selectedTodoId = initialTodoId;
|
|
54
|
+
this.ensureSelection();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
handleInput(data: string): void {
|
|
58
|
+
if (matchesKey(data, "ctrl+c") || matchesKey(data, "escape")) {
|
|
59
|
+
if (this.filterEditing || this.filter) {
|
|
60
|
+
this.filterEditing = false;
|
|
61
|
+
this.filter = "";
|
|
62
|
+
this.listScroll = 0;
|
|
63
|
+
this.ensureSelection();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.done();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (this.filterEditing) {
|
|
70
|
+
if (matchesKey(data, "enter")) {
|
|
71
|
+
this.filterEditing = false;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (matchesKey(data, "backspace") || data === "\x7f") {
|
|
75
|
+
this.filter = this.filter.slice(0, -1);
|
|
76
|
+
this.listScroll = 0;
|
|
77
|
+
this.ensureSelection();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (data.length === 1 && data >= " " && data !== "\x7f") {
|
|
81
|
+
this.filter += data;
|
|
82
|
+
this.listScroll = 0;
|
|
83
|
+
this.ensureSelection();
|
|
84
|
+
}
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (data === "/") {
|
|
88
|
+
this.filterEditing = true;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (matchesKey(data, "up")) this.moveSelection(-1);
|
|
92
|
+
else if (matchesKey(data, "down")) this.moveSelection(1);
|
|
93
|
+
else if (data === "\x1b[5~") this.detailScroll = Math.max(0, this.detailScroll - 8);
|
|
94
|
+
else if (data === "\x1b[6~") this.detailScroll += 8;
|
|
95
|
+
else if (data === "r") this.ensureSelection();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
invalidate(): void {
|
|
99
|
+
this.ensureSelection();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
render(width: number): string[] {
|
|
103
|
+
const th = this.theme;
|
|
104
|
+
const w = Math.max(60, width);
|
|
105
|
+
const inner = Math.max(1, w - 2);
|
|
106
|
+
const leftWidth = Math.min(48, Math.max(28, Math.floor(inner * 0.46)));
|
|
107
|
+
const rightWidth = Math.max(20, inner - leftWidth - 1);
|
|
108
|
+
const rows = this.filteredRows();
|
|
109
|
+
this.ensureSelection();
|
|
110
|
+
this.listScroll = Math.max(0, Math.min(this.listScroll, Math.max(0, rows.length - this.height)));
|
|
111
|
+
const selected = rows.find((node) => node.id === this.selectedTodoId) ?? rows[0];
|
|
112
|
+
const detail = this.detailLines(selected, rightWidth);
|
|
113
|
+
this.detailScroll = Math.max(0, Math.min(this.detailScroll, Math.max(0, detail.length - this.height)));
|
|
114
|
+
|
|
115
|
+
const title = ` ZOB Goal TODOs · ${this.runtimeState.runtimeGoal?.goalId ?? "no-goal"} `;
|
|
116
|
+
const leftRule = "─".repeat(Math.max(0, Math.floor((inner - visibleWidth(title)) / 2)));
|
|
117
|
+
const rightRule = "─".repeat(Math.max(0, inner - visibleWidth(title) - leftRule.length));
|
|
118
|
+
const lines: string[] = [th.fg("border", `╭${leftRule}`) + th.fg("accent", title) + th.fg("border", `${rightRule}╮`)];
|
|
119
|
+
const summary = this.runtimeState.runtimeGoal ? formatGoalTodoSummary(summarizeGoalTodos(this.runtimeState.goalTodos, this.runtimeState.runtimeGoal.goalId)) : "no active goal";
|
|
120
|
+
lines.push(this.row(th.fg("accent", padToWidth(summary, leftWidth)) + th.fg("dim", "│") + padToWidth(selected ? `${selected.path} ${selected.title}` : "No TODO selected", rightWidth), inner));
|
|
121
|
+
lines.push(th.fg("border", `├${"─".repeat(leftWidth)}┼${"─".repeat(rightWidth)}┤`));
|
|
122
|
+
|
|
123
|
+
const visibleRows = rows.slice(this.listScroll, this.listScroll + this.height);
|
|
124
|
+
const visibleDetail = detail.slice(this.detailScroll, this.detailScroll + this.height);
|
|
125
|
+
for (let index = 0; index < this.height; index++) {
|
|
126
|
+
const node = visibleRows[index];
|
|
127
|
+
const left = node ? this.renderNode(node, leftWidth) : index === 0 && rows.length === 0 ? th.fg("warning", "No TODOs match") : "";
|
|
128
|
+
const right = visibleDetail[index] ?? "";
|
|
129
|
+
lines.push(this.row(`${padToWidth(left, leftWidth)}${th.fg("dim", "│")}${padToWidth(right, rightWidth)}`, inner));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const filterInfo = this.filter || this.filterEditing ? ` · filter=${this.filterEditing ? ">" : ""}${this.filter || "<type>"}` : "";
|
|
133
|
+
const footer = `${rows.length} TODO rows${filterInfo} · / filter · ↑↓ select · PgUp/PgDn detail · Esc close`;
|
|
134
|
+
lines.push(th.fg("border", `├${"─".repeat(inner)}┤`));
|
|
135
|
+
lines.push(this.row(th.fg("dim", truncateToWidth(footer, inner, "…")), inner));
|
|
136
|
+
lines.push(th.fg("border", `╰${"─".repeat(inner)}╯`));
|
|
137
|
+
return lines.map((line) => truncateToWidth(line, w, ""));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private row(content: string, width: number): string {
|
|
141
|
+
return this.theme.fg("border", "│") + padToWidth(content, width) + this.theme.fg("border", "│");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private renderNode(node: GoalTodoNode, width: number): string {
|
|
145
|
+
const prefix = " ".repeat(Math.max(0, node.depth - 1));
|
|
146
|
+
const selected = node.id === this.selectedTodoId ? "›" : " ";
|
|
147
|
+
const required = node.required ? "req" : "opt";
|
|
148
|
+
const text = `${selected}${prefix}${statusIcon(node.status)} ${node.path} ${node.title} [${required}]`;
|
|
149
|
+
return this.theme.fg(statusColor(node.status), truncateToWidth(text, width, "…"));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private detailLines(node: GoalTodoNode | undefined, width: number): string[] {
|
|
153
|
+
if (!node) return ["No TODO selected."];
|
|
154
|
+
return [
|
|
155
|
+
`${node.path} ${node.title}`,
|
|
156
|
+
`id: ${node.id}`,
|
|
157
|
+
`status: ${node.status}`,
|
|
158
|
+
`owner: ${node.owner}`,
|
|
159
|
+
`required: ${node.required}`,
|
|
160
|
+
`priority: ${node.priority}`,
|
|
161
|
+
`depth: ${node.depth}`,
|
|
162
|
+
node.parentId ? `parent: ${node.parentId}` : "parent: root",
|
|
163
|
+
node.delegation ? `delegation: ${node.delegation.status} run=${node.delegation.runId ?? "n/a"} agent=${node.delegation.agent ?? "n/a"} depth=${node.delegation.delegationDepth}` : "delegation: none",
|
|
164
|
+
node.blocker ? `blocker: ${node.blocker}` : "blocker: none",
|
|
165
|
+
node.skipReason ? `skip: ${node.skipReason}` : undefined,
|
|
166
|
+
"acceptance:",
|
|
167
|
+
...(node.acceptanceCriteria.length ? node.acceptanceCriteria.map((item) => `- ${item}`) : ["- none"]),
|
|
168
|
+
"evidence_refs:",
|
|
169
|
+
...(node.evidenceRefs.length ? node.evidenceRefs.map((item) => `- ${item}`) : ["- none"]),
|
|
170
|
+
"validation_commands:",
|
|
171
|
+
...(node.validationCommands.length ? node.validationCommands.map((item) => `- ${item}`) : ["- none"]),
|
|
172
|
+
].filter((line): line is string => typeof line === "string").map((line) => truncateToWidth(line, width, "…"));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private filteredRows(): GoalTodoNode[] {
|
|
176
|
+
const filter = this.filter.toLowerCase().trim();
|
|
177
|
+
const rows = goalNodes(this.runtimeState);
|
|
178
|
+
if (!filter) return rows;
|
|
179
|
+
return rows.filter((node) => `${node.id} ${node.path} ${node.title} ${node.status} ${node.owner}`.toLowerCase().includes(filter));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private ensureSelection(): void {
|
|
183
|
+
const rows = this.filteredRows();
|
|
184
|
+
if (rows.length === 0) {
|
|
185
|
+
this.selectedTodoId = undefined;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!this.selectedTodoId || !rows.some((node) => node.id === this.selectedTodoId)) this.selectedTodoId = rows[0].id;
|
|
189
|
+
const selectedIndex = rows.findIndex((node) => node.id === this.selectedTodoId);
|
|
190
|
+
if (selectedIndex < this.listScroll) this.listScroll = selectedIndex;
|
|
191
|
+
if (selectedIndex >= this.listScroll + this.height) this.listScroll = selectedIndex - this.height + 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private moveSelection(delta: number): void {
|
|
195
|
+
const rows = this.filteredRows();
|
|
196
|
+
if (rows.length === 0) return;
|
|
197
|
+
const current = Math.max(0, rows.findIndex((node) => node.id === this.selectedTodoId));
|
|
198
|
+
const next = Math.max(0, Math.min(rows.length - 1, current + delta));
|
|
199
|
+
this.selectedTodoId = rows[next].id;
|
|
200
|
+
this.detailScroll = 0;
|
|
201
|
+
this.ensureSelection();
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export async function showGoalTodoOverlay(ctx: ExtensionContext, state: HarnessRuntimeState, initialTodoId?: string): Promise<void> {
|
|
206
|
+
if (!ctx.hasUI) {
|
|
207
|
+
ctx.ui.notify("Goal TODO overlay requires an interactive UI; use /goal todo tree instead.", "warning");
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
await ctx.ui.custom<void>((_tui, theme, _keybindings, done) => new GoalTodoOverlayComponent(state, theme, done, initialTodoId), {
|
|
211
|
+
overlay: true,
|
|
212
|
+
overlayOptions: { anchor: "center", width: "90%", maxHeight: "80%", margin: 2 },
|
|
213
|
+
});
|
|
214
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { ModeName } from "../types.js";
|
|
2
|
+
|
|
3
|
+
export type ZobModeIntentConfidence = "low" | "medium" | "high";
|
|
4
|
+
export type ZobModeIntentRisk = "low" | "medium" | "high";
|
|
5
|
+
|
|
6
|
+
export interface ZobModeIntent {
|
|
7
|
+
mode: ModeName;
|
|
8
|
+
confidence: ZobModeIntentConfidence;
|
|
9
|
+
reason: string;
|
|
10
|
+
risk?: ZobModeIntentRisk;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ZobModeIntentValidation {
|
|
14
|
+
accepted: boolean;
|
|
15
|
+
reason: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const VALID_MODES: readonly ModeName[] = ["explore", "plan", "implement", "oracle", "factory", "orchestrator"];
|
|
19
|
+
|
|
20
|
+
function modeName(value: string | undefined): ModeName | undefined {
|
|
21
|
+
return VALID_MODES.includes(value as ModeName) ? value as ModeName : undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function confidence(value: string | undefined): ZobModeIntentConfidence | undefined {
|
|
25
|
+
if (value === "low" || value === "medium" || value === "high") return value;
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function risk(value: string | undefined): ZobModeIntentRisk | undefined {
|
|
30
|
+
if (value === "low" || value === "medium" || value === "high") return value;
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function decodeAttribute(value: string): string {
|
|
35
|
+
return value
|
|
36
|
+
.replace(/"/g, '"')
|
|
37
|
+
.replace(/"/g, '"')
|
|
38
|
+
.replace(/'/g, "'")
|
|
39
|
+
.replace(/'/g, "'")
|
|
40
|
+
.replace(/</g, "<")
|
|
41
|
+
.replace(/>/g, ">")
|
|
42
|
+
.replace(/&/g, "&")
|
|
43
|
+
.replace(/\s+/g, " ")
|
|
44
|
+
.trim();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function attributes(source: string): Record<string, string> {
|
|
48
|
+
const parsed: Record<string, string> = {};
|
|
49
|
+
for (const match of source.matchAll(/([a-zA-Z_][a-zA-Z0-9_-]*)\s*=\s*"([^"]*)"/g)) {
|
|
50
|
+
parsed[match[1]!] = decodeAttribute(match[2] ?? "");
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function extractModeIntent(text: string): ZobModeIntent | undefined {
|
|
56
|
+
const match = text.match(/<zob_mode_intent\b([^>]*)\/?\s*>/i);
|
|
57
|
+
if (!match) return undefined;
|
|
58
|
+
const attrs = attributes(match[1] ?? "");
|
|
59
|
+
const mode = modeName(attrs.mode);
|
|
60
|
+
const intentConfidence = confidence(attrs.confidence);
|
|
61
|
+
if (!mode || !intentConfidence) return undefined;
|
|
62
|
+
const reason = (attrs.reason || "same-agent mode intent").slice(0, 240);
|
|
63
|
+
return { mode, confidence: intentConfidence, reason, risk: risk(attrs.risk) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function stripModeIntentMarkup(text: string): string {
|
|
67
|
+
return text
|
|
68
|
+
.replace(/^\s*<zob_mode_intent\b[^>]*\/?\s*>\s*$\n?/gim, "")
|
|
69
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
70
|
+
.trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function looksLikeCompletePlanResponse(text: string): boolean {
|
|
74
|
+
const visible = stripModeIntentMarkup(text);
|
|
75
|
+
if (visible.length < 240) return false;
|
|
76
|
+
const sample = visible.slice(0, 2_400);
|
|
77
|
+
let score = 0;
|
|
78
|
+
if (/^\s*#{1,3}\s+.*\bplan\b/im.test(sample) || /^\s*(?:plan|planning|roadmap)\b/im.test(sample)) score += 2;
|
|
79
|
+
if ((sample.match(/^\s*(?:#{1,4}\s*)?(?:phase|patch|étape|etape|step)\s+\d+/gim) ?? []).length >= 1) score += 1;
|
|
80
|
+
if ((sample.match(/^\s*\d+[.)]\s+/gm) ?? []).length >= 3) score += 1;
|
|
81
|
+
if ((sample.match(/^\s*[-*]\s+/gm) ?? []).length >= 4) score += 1;
|
|
82
|
+
if (/\b(validation|tests?|risques?|risks?|fichiers?|files|scope|p[eé]rim[eè]tre|objectifs?|success looks like|r[eé]sultat attendu)\b/i.test(sample)) score += 1;
|
|
83
|
+
if (/\b(impl[eé]mentation|patch|tdd|ordre recommandé|roadmap|architecture|phases?)\b/i.test(sample)) score += 1;
|
|
84
|
+
if (/<answer>|<files>|<next_steps>/i.test(sample)) score += 1;
|
|
85
|
+
return score >= 3;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const DESTRUCTIVE_COMMAND_PATTERN = /\b(rm\s+-rf|git\s+reset\s+--hard|git\s+clean\b|shutdown\b|reboot\b|mkfs\b)\b/i;
|
|
89
|
+
const SECRET_REF_PATTERN = /(?:\.env\b|~\/\.ssh|~\/\.aws|\bssh\b|\baws\b|\b(?:api[_-]?key|private key|secret key|keys?|credentials?|secrets?)\b)/i;
|
|
90
|
+
const SECRET_TOUCH_ACTION_PATTERN = /\b(read|open|cat|print|show|copy|upload|exfiltrate|inspect|touch|modify|edit|write|access|use|load|source|dump|list)\b/i;
|
|
91
|
+
const NEGATIVE_SAFETY_INSTRUCTION_PATTERN = /\b(?:do not|don't|must not|never|avoid|without|forbidden_paths?|forbidden|deny(?:list)?|no secrets?|ne pas|n['’]?(?:ouvre|ouvre pas|acc[eè]de|acc[eè]de pas|lis|lis pas)|interdit|sans)\b/i;
|
|
92
|
+
const PROMPT_INJECTION_NEGATIVE_PATTERN = /\b(?:do not|don't|must not|never)\s+(?:ignore|obey|follow|respect|comply|listen|refuse|decline)\b/i;
|
|
93
|
+
const NEGATIVE_SECRET_CLAUSE_SPLIT_PATTERN = /\b(?:but|however|except|unless|then|also|or|and)\b|[:,]/i;
|
|
94
|
+
|
|
95
|
+
function hasSecretTouchAction(part: string): boolean {
|
|
96
|
+
return SECRET_REF_PATTERN.test(part) && SECRET_TOUCH_ACTION_PATTERN.test(part);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function isBenignNegativeSecretInstruction(part: string): boolean {
|
|
100
|
+
if (!NEGATIVE_SAFETY_INSTRUCTION_PATTERN.test(part) || !hasSecretTouchAction(part)) return false;
|
|
101
|
+
if (PROMPT_INJECTION_NEGATIVE_PATTERN.test(part)) return false;
|
|
102
|
+
|
|
103
|
+
const [, ...tails] = part.split(NEGATIVE_SECRET_CLAUSE_SPLIT_PATTERN);
|
|
104
|
+
return !tails.some((tail) => hasSecretTouchAction(tail) && !NEGATIVE_SAFETY_INSTRUCTION_PATTERN.test(tail));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function looksSecretTouching(text: string): boolean {
|
|
108
|
+
return text
|
|
109
|
+
.split(/[!?;\n]+|(?<=\S)\.(?=\s|$)/)
|
|
110
|
+
.some((part) => hasSecretTouchAction(part) && !isBenignNegativeSecretInstruction(part));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function looksDestructive(text: string): boolean {
|
|
114
|
+
return DESTRUCTIVE_COMMAND_PATTERN.test(text) || looksSecretTouching(text);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function hasModeEvidence(mode: ModeName, text: string): boolean {
|
|
118
|
+
if (mode === "orchestrator") return /\b(orchestrator|orchestrat(?:e|ion|or)|orchestrer|multi[- ]?agent|lead(?:s)?|worker(?:s)?|chief vision|coordonn(?:e|er|ation)|d[eé]l[eè]gu(?:e|er|ation)|delegat(?:e|ion)|sub[- ]?agents?|subtasks?|work graph|todo graph|graphe de travail|graphe todo)\b/i.test(text);
|
|
119
|
+
if (mode === "factory") return /\b(factory|factory_run|pilot|batch|sentinel|manifest|quarantine|software factory)\b/i.test(text);
|
|
120
|
+
if (mode === "implement") return /\b(update|modify|modifier|change|changer|fix|patch|implement|impl[eé]mente|edit|write|[eé]cris|ajoute|add|create|cr[eé]e|refactor|refactorise|remplace|am[eé]lior(?:e|er)|appliqu(?:e|er)|mets?|mettre|rends?|rendre|fais\s+en\s+sorte)\b/i.test(text);
|
|
121
|
+
if (mode === "oracle") return /\b(review|validate|validation|oracle|no[_-]?ship|v[eé]rifie|audit|qa|risks?|blocker)\b/i.test(text);
|
|
122
|
+
if (mode === "plan") return /\b(plan|design|architecture|propose|roadmap|spec|sp[eé]cifie|comment|how would|strat[eé]gie)\b/i.test(text);
|
|
123
|
+
return /\b(read|explore|inspect|analy[sz]e|cherche|comprends?|trouve|diagnostic)\b/i.test(text);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function validateModeIntent(intent: ZobModeIntent | undefined, currentMode: ModeName, lastUserText = "", assistantText = ""): ZobModeIntentValidation {
|
|
127
|
+
if (!intent) return { accepted: false, reason: "missing or invalid mode intent" };
|
|
128
|
+
if (intent.mode === currentMode) return { accepted: false, reason: "mode already active" };
|
|
129
|
+
if (intent.confidence === "low") return { accepted: false, reason: "low confidence mode intent ignored" };
|
|
130
|
+
if (intent.risk === "high") return { accepted: false, reason: "high-risk mode intent requires explicit user action" };
|
|
131
|
+
if (looksDestructive(`${lastUserText}\n${intent.reason}`)) return { accepted: false, reason: "destructive or secret-touching intent cannot auto-switch mode" };
|
|
132
|
+
if (intent.mode === "plan" && looksLikeCompletePlanResponse(assistantText)) {
|
|
133
|
+
return { accepted: false, reason: "plan mode intent ignored because this response already includes a complete plan" };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const evidence = hasModeEvidence(intent.mode, `${lastUserText}\n${intent.reason}`);
|
|
137
|
+
if ((intent.mode === "implement" || intent.mode === "factory" || intent.mode === "orchestrator") && intent.confidence !== "high" && !evidence) {
|
|
138
|
+
return { accepted: false, reason: `mode ${intent.mode} requires high confidence or explicit evidence` };
|
|
139
|
+
}
|
|
140
|
+
if (intent.mode === "oracle" && intent.confidence === "medium" && !evidence) {
|
|
141
|
+
return { accepted: false, reason: "oracle mode requires review/validation evidence" };
|
|
142
|
+
}
|
|
143
|
+
return { accepted: true, reason: evidence ? "accepted with user-text evidence" : "accepted high-confidence same-agent intent" };
|
|
144
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { ModeName } from "../types.js";
|
|
5
|
+
import { sha256 } from "../utils/hashing.js";
|
|
6
|
+
import { safeFileStem } from "../utils/paths.js";
|
|
7
|
+
import { looksLikeCompletePlanResponse, stripModeIntentMarkup } from "./mode-intent.js";
|
|
8
|
+
|
|
9
|
+
const PLAN_INDEX_SCHEMA = "zob.plan-index.v1";
|
|
10
|
+
const PLAN_CAPTURE_SCHEMA = "zob.plan-capture.v1";
|
|
11
|
+
|
|
12
|
+
type PlanCaptureStatus = "draft";
|
|
13
|
+
|
|
14
|
+
export interface PlanIndexEntry {
|
|
15
|
+
plan_id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
created_at: string;
|
|
18
|
+
mode: ModeName | "unknown";
|
|
19
|
+
status: PlanCaptureStatus;
|
|
20
|
+
relative_path: string;
|
|
21
|
+
body_hash: string;
|
|
22
|
+
user_request_hash: string;
|
|
23
|
+
assistant_output_hash: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface PlanIndex {
|
|
27
|
+
schema: typeof PLAN_INDEX_SCHEMA;
|
|
28
|
+
updated_at: string;
|
|
29
|
+
entries: PlanIndexEntry[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface PlanCaptureInput {
|
|
33
|
+
assistantText: string;
|
|
34
|
+
userText?: string;
|
|
35
|
+
mode?: ModeName;
|
|
36
|
+
now?: Date;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface PlanCaptureResult {
|
|
40
|
+
captured: boolean;
|
|
41
|
+
reason: "captured" | "not_plan" | "duplicate";
|
|
42
|
+
planId?: string;
|
|
43
|
+
title?: string;
|
|
44
|
+
relativePath?: string;
|
|
45
|
+
duplicateOf?: string;
|
|
46
|
+
bodyHash?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
50
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function plansDir(repoRoot: string): string {
|
|
54
|
+
return join(repoRoot, "plans");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function planIndexPath(repoRoot: string): string {
|
|
58
|
+
return join(plansDir(repoRoot), "index.json");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function planIndexMarkdownPath(repoRoot: string): string {
|
|
62
|
+
return join(plansDir(repoRoot), "index.md");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function planReadmePath(repoRoot: string): string {
|
|
66
|
+
return join(plansDir(repoRoot), "README.md");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizePlanText(text: string): string {
|
|
70
|
+
return stripModeIntentMarkup(text).replace(/\s+$/g, "").trim();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function userAskedForPlan(text: string | undefined): boolean {
|
|
74
|
+
if (!text) return false;
|
|
75
|
+
return /\b(plan|planning|roadmap|architecture|strat[eé]gie|sp[eé]cification|spec|conception|design|impl[eé]mentation|ordre de patch|tdd|étapes?|etapes?)\b/i.test(text);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function shouldCapturePlanResponse(input: PlanCaptureInput): boolean {
|
|
79
|
+
const text = normalizePlanText(input.assistantText);
|
|
80
|
+
if (text.length < 160) return false;
|
|
81
|
+
|
|
82
|
+
// Plan capture must be intentional. Delivery reports in implement/oracle/explore modes can
|
|
83
|
+
// share plan-like formatting (files, validation, risks), so formatting alone is not enough.
|
|
84
|
+
if (input.mode !== "plan") return false;
|
|
85
|
+
|
|
86
|
+
if (looksLikeCompletePlanResponse(text)) return true;
|
|
87
|
+
const structuralSignals = [
|
|
88
|
+
/^\s*#{1,3}\s+.*\bplan\b/im.test(text),
|
|
89
|
+
/^\s*(?:plan|roadmap|objectif|objectifs|scope|p[eé]rim[eè]tre)\b/im.test(text),
|
|
90
|
+
(text.match(/^\s*(?:#{1,4}\s*)?(?:phase|patch|étape|etape|step)\s+\d+/gim) ?? []).length >= 1,
|
|
91
|
+
(text.match(/^\s*\d+[.)]\s+/gm) ?? []).length >= 3,
|
|
92
|
+
/\b(validation|tests?|risques?|risks?|fichiers?|files|succ[eè]s|success)\b/i.test(text),
|
|
93
|
+
].filter(Boolean).length;
|
|
94
|
+
return userAskedForPlan(input.userText) ? structuralSignals >= 2 : structuralSignals >= 3;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function extractPlanTitle(text: string): string {
|
|
98
|
+
const normalized = normalizePlanText(text);
|
|
99
|
+
const lines = normalized.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
100
|
+
const heading = lines.find((line) => /^#{1,3}\s+/.test(line));
|
|
101
|
+
const candidate = heading?.replace(/^#{1,3}\s+/, "")
|
|
102
|
+
?? lines.find((line) => /\bplan\b/i.test(line))
|
|
103
|
+
?? lines[0]
|
|
104
|
+
?? "ZOB plan";
|
|
105
|
+
return candidate.replace(/[<>]/g, "").slice(0, 120).trim() || "ZOB plan";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function emptyPlanIndex(now: Date): PlanIndex {
|
|
109
|
+
return { schema: PLAN_INDEX_SCHEMA, updated_at: now.toISOString(), entries: [] };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function asPlanIndexEntry(value: unknown): PlanIndexEntry | undefined {
|
|
113
|
+
if (!isRecord(value)) return undefined;
|
|
114
|
+
const planId = typeof value.plan_id === "string" ? value.plan_id : "";
|
|
115
|
+
const title = typeof value.title === "string" ? value.title : "";
|
|
116
|
+
const createdAt = typeof value.created_at === "string" ? value.created_at : "";
|
|
117
|
+
const mode = typeof value.mode === "string" ? value.mode : "unknown";
|
|
118
|
+
const status = value.status === "draft" ? "draft" : undefined;
|
|
119
|
+
const relativePath = typeof value.relative_path === "string" ? value.relative_path : "";
|
|
120
|
+
const bodyHash = typeof value.body_hash === "string" ? value.body_hash : "";
|
|
121
|
+
const userRequestHash = typeof value.user_request_hash === "string" ? value.user_request_hash : "";
|
|
122
|
+
const assistantOutputHash = typeof value.assistant_output_hash === "string" ? value.assistant_output_hash : "";
|
|
123
|
+
if (!planId || !title || !createdAt || !status || !relativePath || !bodyHash || !userRequestHash || !assistantOutputHash) return undefined;
|
|
124
|
+
return {
|
|
125
|
+
plan_id: planId,
|
|
126
|
+
title,
|
|
127
|
+
created_at: createdAt,
|
|
128
|
+
mode: mode === "explore" || mode === "plan" || mode === "implement" || mode === "oracle" || mode === "factory" ? mode : "unknown",
|
|
129
|
+
status,
|
|
130
|
+
relative_path: relativePath,
|
|
131
|
+
body_hash: bodyHash,
|
|
132
|
+
user_request_hash: userRequestHash,
|
|
133
|
+
assistant_output_hash: assistantOutputHash,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readPlanIndex(repoRoot: string, now: Date): PlanIndex {
|
|
138
|
+
const indexPath = planIndexPath(repoRoot);
|
|
139
|
+
if (!existsSync(indexPath)) return emptyPlanIndex(now);
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(readFileSync(indexPath, "utf8"));
|
|
142
|
+
if (!isRecord(parsed) || parsed.schema !== PLAN_INDEX_SCHEMA || !Array.isArray(parsed.entries)) return emptyPlanIndex(now);
|
|
143
|
+
return {
|
|
144
|
+
schema: PLAN_INDEX_SCHEMA,
|
|
145
|
+
updated_at: typeof parsed.updated_at === "string" ? parsed.updated_at : now.toISOString(),
|
|
146
|
+
entries: parsed.entries.map(asPlanIndexEntry).filter((entry): entry is PlanIndexEntry => Boolean(entry)),
|
|
147
|
+
};
|
|
148
|
+
} catch {
|
|
149
|
+
return emptyPlanIndex(now);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function escapeMarkdownTableCell(value: string): string {
|
|
154
|
+
return value.replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function markdownLink(entry: PlanIndexEntry): string {
|
|
158
|
+
const href = entry.relative_path.replace(/^plans\//, "");
|
|
159
|
+
return `[${escapeMarkdownTableCell(entry.title)}](${href})`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function renderPlanIndexMarkdown(index: PlanIndex): string {
|
|
163
|
+
const rows = index.entries.map((entry) => `| ${entry.created_at.slice(0, 10)} | ${markdownLink(entry)} | ${entry.mode} | ${entry.status} | ${entry.body_hash.slice(0, 12)} |`);
|
|
164
|
+
return [
|
|
165
|
+
"# ZOB Plans Index",
|
|
166
|
+
"",
|
|
167
|
+
"Visible auto-captured planning artifacts. Source user prompts are stored as hashes only; captured assistant plans are stored in the linked Markdown files.",
|
|
168
|
+
"",
|
|
169
|
+
"| Date | Plan | Mode | Status | Hash |",
|
|
170
|
+
"|---|---|---|---|---|",
|
|
171
|
+
...(rows.length > 0 ? rows : ["| — | — | — | — | — |"]),
|
|
172
|
+
"",
|
|
173
|
+
].join("\n");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function ensurePlansReadme(repoRoot: string): void {
|
|
177
|
+
const readmePath = planReadmePath(repoRoot);
|
|
178
|
+
if (existsSync(readmePath)) return;
|
|
179
|
+
writeFileSync(readmePath, [
|
|
180
|
+
"# ZOB Plans",
|
|
181
|
+
"",
|
|
182
|
+
"This directory stores visible planning artifacts automatically captured by the ZOB harness.",
|
|
183
|
+
"",
|
|
184
|
+
"- `index.md` is the human-readable index.",
|
|
185
|
+
"- `index.json` is the machine-readable duplicate-detection manifest.",
|
|
186
|
+
"- Date folders contain the captured Markdown plans.",
|
|
187
|
+
"- Source user prompts are not persisted here; hashes are used for correlation.",
|
|
188
|
+
"",
|
|
189
|
+
].join("\n"), "utf8");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function writePlanIndex(repoRoot: string, index: PlanIndex): void {
|
|
193
|
+
mkdirSync(plansDir(repoRoot), { recursive: true });
|
|
194
|
+
ensurePlansReadme(repoRoot);
|
|
195
|
+
writeFileSync(planIndexPath(repoRoot), JSON.stringify(index, null, 2), "utf8");
|
|
196
|
+
writeFileSync(planIndexMarkdownPath(repoRoot), renderPlanIndexMarkdown(index), "utf8");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function quoted(value: string): string {
|
|
200
|
+
return JSON.stringify(value);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function renderPlanMarkdown(entry: PlanIndexEntry, assistantText: string): string {
|
|
204
|
+
return [
|
|
205
|
+
"---",
|
|
206
|
+
`schema: ${quoted(PLAN_CAPTURE_SCHEMA)}`,
|
|
207
|
+
`plan_id: ${quoted(entry.plan_id)}`,
|
|
208
|
+
`created_at: ${quoted(entry.created_at)}`,
|
|
209
|
+
`mode: ${quoted(entry.mode)}`,
|
|
210
|
+
`status: ${quoted(entry.status)}`,
|
|
211
|
+
`title: ${quoted(entry.title)}`,
|
|
212
|
+
`user_request_hash: ${quoted(entry.user_request_hash)}`,
|
|
213
|
+
`assistant_output_hash: ${quoted(entry.assistant_output_hash)}`,
|
|
214
|
+
`body_hash: ${quoted(entry.body_hash)}`,
|
|
215
|
+
"---",
|
|
216
|
+
"",
|
|
217
|
+
`# ${entry.title}`,
|
|
218
|
+
"",
|
|
219
|
+
"> Captured automatically by the ZOB harness. Source user prompt text is not persisted in this artifact; hashes are retained for duplicate detection.",
|
|
220
|
+
"",
|
|
221
|
+
"## Captured Plan",
|
|
222
|
+
"",
|
|
223
|
+
assistantText.trim(),
|
|
224
|
+
"",
|
|
225
|
+
"## Metadata",
|
|
226
|
+
"",
|
|
227
|
+
`- plan_id: ${entry.plan_id}`,
|
|
228
|
+
`- mode: ${entry.mode}`,
|
|
229
|
+
`- status: ${entry.status}`,
|
|
230
|
+
`- body_hash: ${entry.body_hash}`,
|
|
231
|
+
"",
|
|
232
|
+
].join("\n");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function capturePlanArtifact(repoRoot: string, input: PlanCaptureInput): PlanCaptureResult {
|
|
236
|
+
const assistantText = normalizePlanText(input.assistantText);
|
|
237
|
+
if (!shouldCapturePlanResponse({ ...input, assistantText })) return { captured: false, reason: "not_plan" };
|
|
238
|
+
|
|
239
|
+
const now = input.now ?? new Date();
|
|
240
|
+
const createdAt = now.toISOString();
|
|
241
|
+
const userText = input.userText?.trim() ?? "";
|
|
242
|
+
const bodyHash = sha256(JSON.stringify({ userText, assistantText }));
|
|
243
|
+
const index = readPlanIndex(repoRoot, now);
|
|
244
|
+
const duplicate = index.entries.find((entry) => entry.body_hash === bodyHash);
|
|
245
|
+
if (duplicate) return { captured: false, reason: "duplicate", duplicateOf: duplicate.relative_path, bodyHash };
|
|
246
|
+
|
|
247
|
+
const title = extractPlanTitle(assistantText);
|
|
248
|
+
const dateDir = createdAt.slice(0, 10);
|
|
249
|
+
const planId = safeFileStem(`plan-${createdAt.replace(/\D/g, "").slice(0, 14)}-${bodyHash.slice(0, 8)}`);
|
|
250
|
+
const slug = safeFileStem(title).toLowerCase();
|
|
251
|
+
const relativePath = `plans/${dateDir}/${planId}-${slug}.md`;
|
|
252
|
+
const absolutePath = join(repoRoot, relativePath);
|
|
253
|
+
const entry: PlanIndexEntry = {
|
|
254
|
+
plan_id: planId,
|
|
255
|
+
title,
|
|
256
|
+
created_at: createdAt,
|
|
257
|
+
mode: input.mode ?? "unknown",
|
|
258
|
+
status: "draft",
|
|
259
|
+
relative_path: relativePath,
|
|
260
|
+
body_hash: bodyHash,
|
|
261
|
+
user_request_hash: sha256(userText),
|
|
262
|
+
assistant_output_hash: sha256(assistantText),
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
266
|
+
writeFileSync(absolutePath, renderPlanMarkdown(entry, assistantText), "utf8");
|
|
267
|
+
const updatedIndex: PlanIndex = { schema: PLAN_INDEX_SCHEMA, updated_at: createdAt, entries: [entry, ...index.entries] };
|
|
268
|
+
writePlanIndex(repoRoot, updatedIndex);
|
|
269
|
+
return { captured: true, reason: "captured", planId, title, relativePath, bodyHash };
|
|
270
|
+
}
|