session-orchestrator 3.16.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/.claude-plugin/marketplace.json +29 -0
- package/.claude-plugin/plugin.json +18 -0
- package/.codex-plugin/agents/explorer.toml +14 -0
- package/.codex-plugin/agents/session-reviewer.toml +23 -0
- package/.codex-plugin/agents/wave-worker.toml +15 -0
- package/.codex-plugin/config.toml +20 -0
- package/.codex-plugin/plugin.json +37 -0
- package/.cursor/rules/000-session-orchestrator.mdc +73 -0
- package/.cursor/rules/010-session-workflow.mdc +170 -0
- package/.cursor/rules/020-quality-gates.mdc +128 -0
- package/.cursor/rules/030-wave-execution.mdc +216 -0
- package/.cursor/rules/040-discovery.mdc +242 -0
- package/.cursor/rules/050-plan.mdc +235 -0
- package/.cursor/rules/060-evolve.mdc +232 -0
- package/.cursor/rules/070-gitlab-ops.mdc +246 -0
- package/.cursor/rules/080-ecosystem-health.mdc +145 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +1544 -0
- package/LICENSE +21 -0
- package/NOTICE +64 -0
- package/README.md +242 -0
- package/SECURITY.md +90 -0
- package/agents/AGENTS.md +136 -0
- package/agents/analyst.md +99 -0
- package/agents/architect-reviewer.md +93 -0
- package/agents/code-implementer.md +106 -0
- package/agents/db-specialist.md +104 -0
- package/agents/dialectic-deriver.md +139 -0
- package/agents/docs-writer.md +113 -0
- package/agents/eval-judge.md +146 -0
- package/agents/memory-proposal-collector.md +297 -0
- package/agents/qa-strategist.md +102 -0
- package/agents/schemas/analyst.schema.json +46 -0
- package/agents/schemas/architect-reviewer.schema.json +50 -0
- package/agents/schemas/code-implementer.schema.json +61 -0
- package/agents/schemas/db-specialist.schema.json +80 -0
- package/agents/schemas/docs-writer.schema.json +56 -0
- package/agents/schemas/persona-panel-sidecar.schema.json +245 -0
- package/agents/schemas/qa-strategist.schema.json +46 -0
- package/agents/schemas/security-reviewer.schema.json +86 -0
- package/agents/schemas/session-reviewer.schema.json +69 -0
- package/agents/schemas/test-writer.schema.json +69 -0
- package/agents/schemas/ui-developer.schema.json +90 -0
- package/agents/schemas/ux-evaluator.schema.json +51 -0
- package/agents/security-reviewer.md +236 -0
- package/agents/session-reviewer.md +201 -0
- package/agents/skill-applied-judge.md +122 -0
- package/agents/test-writer.md +123 -0
- package/agents/ui-developer.md +109 -0
- package/agents/ux-evaluator.md +161 -0
- package/assets/icon.svg +11 -0
- package/assets/og-card.png +0 -0
- package/assets/og-card.svg +47 -0
- package/commands/autopilot-multi.md +74 -0
- package/commands/autopilot.md +80 -0
- package/commands/bootstrap.md +56 -0
- package/commands/brainstorm.md +48 -0
- package/commands/close.md +24 -0
- package/commands/debug.md +36 -0
- package/commands/discovery.md +32 -0
- package/commands/dispatcher.md +59 -0
- package/commands/eval.md +28 -0
- package/commands/evolve.md +10 -0
- package/commands/go.md +41 -0
- package/commands/grill.md +45 -0
- package/commands/harness-audit.md +26 -0
- package/commands/memory-cleanup.md +25 -0
- package/commands/persona-panel.md +121 -0
- package/commands/plan.md +15 -0
- package/commands/portfolio.md +97 -0
- package/commands/reconcile.md +23 -0
- package/commands/repo-audit.md +24 -0
- package/commands/session.md +30 -0
- package/commands/spinout.md +15 -0
- package/commands/sunset-review.md +27 -0
- package/commands/templates-ack.md +96 -0
- package/commands/test.md +97 -0
- package/docs/README.md +105 -0
- package/docs/USER-GUIDE.md +1403 -0
- package/docs/ci-setup.md +81 -0
- package/docs/codex-setup.md +142 -0
- package/docs/components.md +74 -0
- package/docs/cursor-setup.md +104 -0
- package/docs/events-schema.md +81 -0
- package/docs/migration-v3.md +148 -0
- package/docs/owner-config-schema.md +154 -0
- package/docs/persona-panel.md +433 -0
- package/docs/pi-setup.md +115 -0
- package/docs/plugin-architecture-v3.md +296 -0
- package/docs/pm-skills-marketplace.md +114 -0
- package/docs/policy-cache-validation-2026-04-28.md +118 -0
- package/docs/rule-authoring.md +316 -0
- package/docs/session-config-reference.md +1439 -0
- package/docs/session-config-template.md +961 -0
- package/docs/vault-docs-architecture.md +297 -0
- package/hooks/_lib/lock-bootstrap.mjs +272 -0
- package/hooks/_lib/lock-reconcile.mjs +93 -0
- package/hooks/_lib/profile-gate.mjs +95 -0
- package/hooks/_lib/transcript-history.mjs +211 -0
- package/hooks/agent-teams-h3-test.sh +362 -0
- package/hooks/config-protection.mjs +0 -0
- package/hooks/cwd-change-restore.mjs +131 -0
- package/hooks/enforce-commands.mjs +179 -0
- package/hooks/enforce-scope.mjs +273 -0
- package/hooks/hooks-codex.json +60 -0
- package/hooks/hooks-cursor.json +15 -0
- package/hooks/hooks-pi.json +115 -0
- package/hooks/hooks.json +215 -0
- package/hooks/loop-guard.mjs +260 -0
- package/hooks/on-session-end.mjs +217 -0
- package/hooks/on-session-start.mjs +660 -0
- package/hooks/on-stop.mjs +294 -0
- package/hooks/operator-steer.mjs +64 -0
- package/hooks/post-edit-validate.mjs +225 -0
- package/hooks/post-subagent-discovery-validator.mjs +398 -0
- package/hooks/post-tool-batch-wave-signal.mjs +328 -0
- package/hooks/post-tool-failure-corrective-context.mjs +248 -0
- package/hooks/post-tooluse-frontend-slop.mjs +184 -0
- package/hooks/pre-bash-destructive-guard.mjs +515 -0
- package/hooks/pre-bash-memory-propose-audit.mjs +206 -0
- package/hooks/pre-bash-staging-fence.mjs +223 -0
- package/hooks/pre-bash-templates-first.mjs +404 -0
- package/hooks/run-node.sh +72 -0
- package/hooks/skill-invocation-telemetry.mjs +99 -0
- package/hooks/subagent-telemetry.mjs +249 -0
- package/hooks/wave-scope-commit-guard.mjs +191 -0
- package/monitors/monitors.json +14 -0
- package/output-styles/finding-report.md +48 -0
- package/output-styles/session-report.md +53 -0
- package/output-styles/wave-summary.md +38 -0
- package/package.json +94 -0
- package/pi/extensions/session-orchestrator.ts +25 -0
- package/pi/prompts/autopilot-multi.md +12 -0
- package/pi/prompts/autopilot.md +12 -0
- package/pi/prompts/bootstrap.md +12 -0
- package/pi/prompts/brainstorm.md +12 -0
- package/pi/prompts/close.md +11 -0
- package/pi/prompts/debug.md +12 -0
- package/pi/prompts/discovery.md +12 -0
- package/pi/prompts/dispatcher.md +12 -0
- package/pi/prompts/eval.md +12 -0
- package/pi/prompts/evolve.md +12 -0
- package/pi/prompts/go.md +12 -0
- package/pi/prompts/grill.md +12 -0
- package/pi/prompts/harness-audit.md +12 -0
- package/pi/prompts/memory-cleanup.md +12 -0
- package/pi/prompts/persona-panel.md +12 -0
- package/pi/prompts/plan.md +12 -0
- package/pi/prompts/portfolio.md +12 -0
- package/pi/prompts/reconcile.md +12 -0
- package/pi/prompts/repo-audit.md +12 -0
- package/pi/prompts/session.md +12 -0
- package/pi/prompts/spinout.md +12 -0
- package/pi/prompts/sunset-review.md +12 -0
- package/pi/prompts/templates-ack.md +12 -0
- package/pi/prompts/test.md +12 -0
- package/rules/_index.md +51 -0
- package/rules/always-on/commit-discipline.md +26 -0
- package/rules/always-on/npm-quality-gates.md +26 -0
- package/rules/always-on/parallel-sessions.md +43 -0
- package/rules/opt-in-domain/prompt-caching.md +270 -0
- package/rules/opt-in-stack/backend-data.md +188 -0
- package/rules/opt-in-stack/backend.md +390 -0
- package/rules/opt-in-stack/frontend.md +98 -0
- package/rules/opt-in-stack/security-web.md +194 -0
- package/rules/opt-in-stack/swift.md +65 -0
- package/scripts/archive-closed-prds.mjs +416 -0
- package/scripts/autopilot-multi.mjs +802 -0
- package/scripts/autopilot.mjs +383 -0
- package/scripts/backfill-abandoned-sessions.mjs +265 -0
- package/scripts/backfill-learnings-expires.mjs +196 -0
- package/scripts/backfill-learnings.mjs +203 -0
- package/scripts/backfill-sessions.mjs +282 -0
- package/scripts/check-doc-consistency.sh +279 -0
- package/scripts/check-package-manager.mjs +445 -0
- package/scripts/ci/assert-vitest-green.mjs +267 -0
- package/scripts/codex-install.mjs +435 -0
- package/scripts/compute-grounding-injection.sh +186 -0
- package/scripts/cursor-install.mjs +113 -0
- package/scripts/dialectic-deriver.mjs +573 -0
- package/scripts/emit-event.mjs +160 -0
- package/scripts/emit-session.mjs +212 -0
- package/scripts/eval-session.mjs +262 -0
- package/scripts/export-hw-learnings.mjs +437 -0
- package/scripts/gc-stale-worktrees.mjs +666 -0
- package/scripts/generate-pi-prompts.mjs +127 -0
- package/scripts/harness-audit.mjs +287 -0
- package/scripts/lib/agent-frontmatter.mjs +266 -0
- package/scripts/lib/agent-output-schema.mjs +166 -0
- package/scripts/lib/agent-status.mjs +303 -0
- package/scripts/lib/ajv-loader.mjs +34 -0
- package/scripts/lib/auto-dialectic.mjs +382 -0
- package/scripts/lib/auto-dream.mjs +471 -0
- package/scripts/lib/autonomy/suitability.mjs +212 -0
- package/scripts/lib/autopilot/dep-graph.mjs +417 -0
- package/scripts/lib/autopilot/durable-telemetry.mjs +121 -0
- package/scripts/lib/autopilot/flags.mjs +104 -0
- package/scripts/lib/autopilot/kill-switches.mjs +174 -0
- package/scripts/lib/autopilot/loop.mjs +320 -0
- package/scripts/lib/autopilot/mr-draft.mjs +520 -0
- package/scripts/lib/autopilot/multi-killswitch.mjs +184 -0
- package/scripts/lib/autopilot/recent-runs.mjs +106 -0
- package/scripts/lib/autopilot/stall-sampler.mjs +97 -0
- package/scripts/lib/autopilot/telemetry.mjs +224 -0
- package/scripts/lib/autopilot/worktree-pipeline.mjs +605 -0
- package/scripts/lib/autopilot-telemetry.mjs +11 -0
- package/scripts/lib/autopilot.mjs +39 -0
- package/scripts/lib/backlog-scan.mjs +179 -0
- package/scripts/lib/bootstrap-lock-freshness.mjs +260 -0
- package/scripts/lib/bootstrap-lock-refresh.mjs +186 -0
- package/scripts/lib/build-live-signals.mjs +150 -0
- package/scripts/lib/ci-status-banner.mjs +425 -0
- package/scripts/lib/claude-md-budget-lint.mjs +246 -0
- package/scripts/lib/cli-flags.mjs +158 -0
- package/scripts/lib/codex/plugin-contract.mjs +610 -0
- package/scripts/lib/cold-start-detector.mjs +240 -0
- package/scripts/lib/command-blocker.mjs +458 -0
- package/scripts/lib/common.mjs +333 -0
- package/scripts/lib/config/auto-dream.mjs +77 -0
- package/scripts/lib/config/block-header.mjs +94 -0
- package/scripts/lib/config/broken-window.mjs +114 -0
- package/scripts/lib/config/coercers.mjs +248 -0
- package/scripts/lib/config/cold-start.mjs +92 -0
- package/scripts/lib/config/config-protection.mjs +120 -0
- package/scripts/lib/config/cross-repo.mjs +104 -0
- package/scripts/lib/config/custom-phases.mjs +213 -0
- package/scripts/lib/config/dialectic.mjs +92 -0
- package/scripts/lib/config/discovery-validator.mjs +75 -0
- package/scripts/lib/config/dispatcher-autonomy-capture.mjs +240 -0
- package/scripts/lib/config/dispatcher-autonomy.mjs +152 -0
- package/scripts/lib/config/docs-orchestrator.mjs +90 -0
- package/scripts/lib/config/docs-staleness.mjs +96 -0
- package/scripts/lib/config/drift-check.mjs +155 -0
- package/scripts/lib/config/eval.mjs +130 -0
- package/scripts/lib/config/events-rotation.mjs +74 -0
- package/scripts/lib/config/evolve.mjs +308 -0
- package/scripts/lib/config/frontend-slop-hook.mjs +104 -0
- package/scripts/lib/config/gitlab-portfolio.mjs +150 -0
- package/scripts/lib/config/handover-gate.mjs +106 -0
- package/scripts/lib/config/host-paths.mjs +76 -0
- package/scripts/lib/config/io.mjs +54 -0
- package/scripts/lib/config/loop-guard.mjs +117 -0
- package/scripts/lib/config/memory.mjs +150 -0
- package/scripts/lib/config/persona-gate-wave.mjs +258 -0
- package/scripts/lib/config/reconcile.mjs +205 -0
- package/scripts/lib/config/section-extractor.mjs +100 -0
- package/scripts/lib/config/skill-evolution.mjs +112 -0
- package/scripts/lib/config/slopcheck.mjs +99 -0
- package/scripts/lib/config/state-md-lock.mjs +83 -0
- package/scripts/lib/config/templates-first.mjs +94 -0
- package/scripts/lib/config/test.mjs +113 -0
- package/scripts/lib/config/vault-integration.mjs +201 -0
- package/scripts/lib/config/vault-mirror-quality.mjs +99 -0
- package/scripts/lib/config/vault-staleness.mjs +84 -0
- package/scripts/lib/config/vault-sync.mjs +96 -0
- package/scripts/lib/config/verification-auto-fix.mjs +84 -0
- package/scripts/lib/config/wave-reviewers.mjs +133 -0
- package/scripts/lib/config-schema.mjs +345 -0
- package/scripts/lib/config.mjs +474 -0
- package/scripts/lib/convergence-monitor.mjs +389 -0
- package/scripts/lib/coordinator-snapshot.mjs +371 -0
- package/scripts/lib/crypto-digest-utils.mjs +91 -0
- package/scripts/lib/discovery/helpers.mjs +127 -0
- package/scripts/lib/discovery/triage-state.mjs +279 -0
- package/scripts/lib/dispatcher/cli.mjs +257 -0
- package/scripts/lib/dispatcher/enumerate.mjs +243 -0
- package/scripts/lib/dispatcher/rank.mjs +363 -0
- package/scripts/lib/ecosystem-health.mjs +224 -0
- package/scripts/lib/ecosystem-wizard/ci-detector.mjs +18 -0
- package/scripts/lib/ecosystem-wizard/config-parser.mjs +54 -0
- package/scripts/lib/ecosystem-wizard/config-writer.mjs +287 -0
- package/scripts/lib/ecosystem-wizard/package-manager-detector.mjs +42 -0
- package/scripts/lib/ecosystem-wizard/wizard-prompt.mjs +246 -0
- package/scripts/lib/ecosystem-wizard.mjs +48 -0
- package/scripts/lib/env-check.mjs +89 -0
- package/scripts/lib/eval/engine.mjs +605 -0
- package/scripts/lib/eval/judge.mjs +433 -0
- package/scripts/lib/eval/report.mjs +367 -0
- package/scripts/lib/eval/schema.mjs +618 -0
- package/scripts/lib/eval/session-resolve.mjs +137 -0
- package/scripts/lib/eval/sink.mjs +77 -0
- package/scripts/lib/events-rotation.mjs +86 -0
- package/scripts/lib/events-schema.mjs +81 -0
- package/scripts/lib/events.mjs +80 -0
- package/scripts/lib/evolve/autonomy-verdict.mjs +461 -0
- package/scripts/lib/evolve/autopilot-effectiveness.mjs +293 -0
- package/scripts/lib/exclusivity-matrix.mjs +68 -0
- package/scripts/lib/fetch-baseline.mjs +311 -0
- package/scripts/lib/file-lock.mjs +512 -0
- package/scripts/lib/frontend-detect/detect.mjs +138 -0
- package/scripts/lib/frontend-detect/rules.mjs +295 -0
- package/scripts/lib/frontmatter-guard.mjs +241 -0
- package/scripts/lib/gates/echo-stub-detect.mjs +39 -0
- package/scripts/lib/gates/gate-baseline.mjs +42 -0
- package/scripts/lib/gates/gate-full.mjs +85 -0
- package/scripts/lib/gates/gate-helpers.mjs +231 -0
- package/scripts/lib/gates/gate-incremental.mjs +76 -0
- package/scripts/lib/gates/gate-per-file.mjs +55 -0
- package/scripts/lib/gitlab-ops/stale-mr-sweep.mjs +447 -0
- package/scripts/lib/gitlab-portfolio/aggregator.mjs +383 -0
- package/scripts/lib/gitlab-portfolio/cli.mjs +428 -0
- package/scripts/lib/gitlab-portfolio/markdown-writer.mjs +289 -0
- package/scripts/lib/gitlab-portfolio/vcs-detect.mjs +182 -0
- package/scripts/lib/handover-gate.mjs +222 -0
- package/scripts/lib/hardening.mjs +43 -0
- package/scripts/lib/hardware-pattern-detector.mjs +238 -0
- package/scripts/lib/harness-audit/categories/category1.mjs +123 -0
- package/scripts/lib/harness-audit/categories/category2.mjs +145 -0
- package/scripts/lib/harness-audit/categories/category3.mjs +143 -0
- package/scripts/lib/harness-audit/categories/category4.mjs +202 -0
- package/scripts/lib/harness-audit/categories/category5.mjs +152 -0
- package/scripts/lib/harness-audit/categories/category6.mjs +211 -0
- package/scripts/lib/harness-audit/categories/category7.mjs +125 -0
- package/scripts/lib/harness-audit/categories/category8.mjs +328 -0
- package/scripts/lib/harness-audit/categories/category9.mjs +294 -0
- package/scripts/lib/harness-audit/categories/helpers.mjs +165 -0
- package/scripts/lib/harness-audit/categories.mjs +19 -0
- package/scripts/lib/historical-guard.mjs +15 -0
- package/scripts/lib/host-identity.mjs +262 -0
- package/scripts/lib/instruction-budget-guard.mjs +332 -0
- package/scripts/lib/io.mjs +304 -0
- package/scripts/lib/issue-close-strip-labels.mjs +161 -0
- package/scripts/lib/language-mappers/README.md +57 -0
- package/scripts/lib/language-mappers/index.mjs +165 -0
- package/scripts/lib/language-mappers/markdown.mjs +149 -0
- package/scripts/lib/language-mappers/python.mjs +249 -0
- package/scripts/lib/language-mappers/swift.mjs +201 -0
- package/scripts/lib/language-mappers/typescript.mjs +433 -0
- package/scripts/lib/learnings/expiry-sweep.mjs +164 -0
- package/scripts/lib/learnings/filters.mjs +43 -0
- package/scripts/lib/learnings/io.mjs +255 -0
- package/scripts/lib/learnings/schema.mjs +518 -0
- package/scripts/lib/learnings/surface.mjs +207 -0
- package/scripts/lib/learnings.mjs +42 -0
- package/scripts/lib/lock-reaper.mjs +648 -0
- package/scripts/lib/locks/index.mjs +31 -0
- package/scripts/lib/locks/lock-body.mjs +62 -0
- package/scripts/lib/locks/staging-fence-lock.mjs +267 -0
- package/scripts/lib/locks/state-md-lock.mjs +351 -0
- package/scripts/lib/loop-readiness-banner.mjs +144 -0
- package/scripts/lib/memory-banner.mjs +478 -0
- package/scripts/lib/memory-cleanup/worktree-sweep.mjs +108 -0
- package/scripts/lib/memory-cleanup-stamp.mjs +56 -0
- package/scripts/lib/memory-paths.mjs +31 -0
- package/scripts/lib/memory-proposals/collector.mjs +334 -0
- package/scripts/lib/memory-proposals/schema.mjs +289 -0
- package/scripts/lib/memory-proposals/sink.mjs +507 -0
- package/scripts/lib/memory-proposals/store.mjs +441 -0
- package/scripts/lib/mission-status-schema.mjs +114 -0
- package/scripts/lib/mode-selector/alternatives.mjs +64 -0
- package/scripts/lib/mode-selector/constants.mjs +29 -0
- package/scripts/lib/mode-selector/context-pressure.mjs +157 -0
- package/scripts/lib/mode-selector/rationale.mjs +55 -0
- package/scripts/lib/mode-selector/scoring.mjs +221 -0
- package/scripts/lib/mode-selector-accuracy.mjs +121 -0
- package/scripts/lib/mode-selector.mjs +160 -0
- package/scripts/lib/multi-provider-build/providers.mjs +64 -0
- package/scripts/lib/multi-provider-build/templating.mjs +130 -0
- package/scripts/lib/named-baseline-resolver.mjs +233 -0
- package/scripts/lib/named-vault-resolver.mjs +433 -0
- package/scripts/lib/owner-config/coerce.mjs +29 -0
- package/scripts/lib/owner-config/constants.mjs +21 -0
- package/scripts/lib/owner-config/defaults.mjs +50 -0
- package/scripts/lib/owner-config/error.mjs +19 -0
- package/scripts/lib/owner-config/index.mjs +13 -0
- package/scripts/lib/owner-config/merge.mjs +52 -0
- package/scripts/lib/owner-config/validate.mjs +259 -0
- package/scripts/lib/owner-config-banner.mjs +126 -0
- package/scripts/lib/owner-config-loader.mjs +159 -0
- package/scripts/lib/owner-config.example.yaml +72 -0
- package/scripts/lib/owner-config.mjs +28 -0
- package/scripts/lib/owner-interview.mjs +243 -0
- package/scripts/lib/owner-yaml.mjs +571 -0
- package/scripts/lib/package-manager.mjs +160 -0
- package/scripts/lib/path-utils.mjs +217 -0
- package/scripts/lib/peer-cards/merger.mjs +310 -0
- package/scripts/lib/peer-cards/reader.mjs +125 -0
- package/scripts/lib/peer-cards/schema.mjs +230 -0
- package/scripts/lib/peer-cards/staleness-banner.mjs +86 -0
- package/scripts/lib/peer-cards/writer.mjs +138 -0
- package/scripts/lib/peer-discovery.mjs +200 -0
- package/scripts/lib/persona-panel/catalog-loader.mjs +577 -0
- package/scripts/lib/persona-panel/consolidator.mjs +370 -0
- package/scripts/lib/persona-panel/persona-runner.mjs +375 -0
- package/scripts/lib/persona-panel/threshold.mjs +130 -0
- package/scripts/lib/pi-hook-bridge.mjs +328 -0
- package/scripts/lib/platform.mjs +266 -0
- package/scripts/lib/playwright-driver/runner.mjs +297 -0
- package/scripts/lib/plugin-root.mjs +210 -0
- package/scripts/lib/pre-dispatch-check.mjs +126 -0
- package/scripts/lib/product-repo-detect.mjs +121 -0
- package/scripts/lib/profiles/registry.mjs +176 -0
- package/scripts/lib/profiles/schema.mjs +209 -0
- package/scripts/lib/qg-command-drift-banner.mjs +88 -0
- package/scripts/lib/quality-gate/diagnostics.mjs +92 -0
- package/scripts/lib/quality-gate.mjs +536 -0
- package/scripts/lib/quality-gates-cache.mjs +228 -0
- package/scripts/lib/quality-gates-policy.mjs +95 -0
- package/scripts/lib/recommendations-v0.mjs +156 -0
- package/scripts/lib/reconcile/eligibility.mjs +203 -0
- package/scripts/lib/reconcile/emitter.mjs +244 -0
- package/scripts/lib/reconcile/engine.mjs +412 -0
- package/scripts/lib/reconcile/idempotency.mjs +239 -0
- package/scripts/lib/reconcile/renderer.mjs +211 -0
- package/scripts/lib/reconcile/writer.mjs +293 -0
- package/scripts/lib/reconcile-nudge-banner.mjs +284 -0
- package/scripts/lib/resource-probe/evaluate.mjs +190 -0
- package/scripts/lib/resource-probe/parsers.mjs +181 -0
- package/scripts/lib/resource-probe/probe-platform.mjs +300 -0
- package/scripts/lib/resource-probe.mjs +95 -0
- package/scripts/lib/rule-loader.mjs +552 -0
- package/scripts/lib/rules-sync.mjs +439 -0
- package/scripts/lib/scope-gate.mjs +496 -0
- package/scripts/lib/session-close-backfill.mjs +539 -0
- package/scripts/lib/session-discovery.mjs +256 -0
- package/scripts/lib/session-end/phase-skip.mjs +357 -0
- package/scripts/lib/session-end/worktree-cleanup.mjs +112 -0
- package/scripts/lib/session-id.mjs +362 -0
- package/scripts/lib/session-lock.mjs +703 -0
- package/scripts/lib/session-registry.mjs +355 -0
- package/scripts/lib/session-schema/aliases.mjs +71 -0
- package/scripts/lib/session-schema/constants.mjs +115 -0
- package/scripts/lib/session-schema/normalizer.mjs +66 -0
- package/scripts/lib/session-schema/timestamps.mjs +64 -0
- package/scripts/lib/session-schema/validator.mjs +453 -0
- package/scripts/lib/session-schema.mjs +71 -0
- package/scripts/lib/session-token-rollup.mjs +137 -0
- package/scripts/lib/sessions-staleness-banner.mjs +247 -0
- package/scripts/lib/skill-evolution/blast-radius-classifier.mjs +114 -0
- package/scripts/lib/skill-evolution/candidate-intake.mjs +270 -0
- package/scripts/lib/skill-evolution/config-validation-gate.mjs +279 -0
- package/scripts/lib/skill-evolution/engine.mjs +719 -0
- package/scripts/lib/skill-evolution/idempotency.mjs +279 -0
- package/scripts/lib/skill-evolution/mr-opener.mjs +507 -0
- package/scripts/lib/skill-health/join.mjs +181 -0
- package/scripts/lib/skill-health/score.mjs +123 -0
- package/scripts/lib/skill-invocations-schema.mjs +214 -0
- package/scripts/lib/skill-judge.mjs +348 -0
- package/scripts/lib/skill-judgments-schema.mjs +264 -0
- package/scripts/lib/slopcheck.mjs +501 -0
- package/scripts/lib/soul-resolve.mjs +118 -0
- package/scripts/lib/spiral-carryover.mjs +495 -0
- package/scripts/lib/state-md/body-sections.mjs +851 -0
- package/scripts/lib/state-md/frontmatter-mutators.mjs +453 -0
- package/scripts/lib/state-md/mission-status.mjs +247 -0
- package/scripts/lib/state-md/recommendations.mjs +57 -0
- package/scripts/lib/state-md/yaml-parser.mjs +234 -0
- package/scripts/lib/state-md-peer-guard.mjs +232 -0
- package/scripts/lib/state-md.mjs +53 -0
- package/scripts/lib/subagents-schema.mjs +309 -0
- package/scripts/lib/sunset/walker.mjs +1192 -0
- package/scripts/lib/test-runner/artifact-paths.mjs +94 -0
- package/scripts/lib/test-runner/fingerprint.mjs +33 -0
- package/scripts/lib/test-runner/issue-reconcile.mjs +770 -0
- package/scripts/lib/tmux-layout/layouts.mjs +224 -0
- package/scripts/lib/tmux-layout/telemetry-stats.mjs +100 -0
- package/scripts/lib/tmux-layout/telemetry.mjs +88 -0
- package/scripts/lib/tmux-layout/tmux-shell.mjs +82 -0
- package/scripts/lib/tmux-layout/vcs-detector.mjs +88 -0
- package/scripts/lib/validate/check-agents.mjs +457 -0
- package/scripts/lib/validate/check-codex-plugin.mjs +37 -0
- package/scripts/lib/validate/check-commands.mjs +148 -0
- package/scripts/lib/validate/check-component-paths.mjs +112 -0
- package/scripts/lib/validate/check-dead-bridge.mjs +180 -0
- package/scripts/lib/validate/check-hooks-symmetry.mjs +258 -0
- package/scripts/lib/validate/check-json-files.mjs +116 -0
- package/scripts/lib/validate/check-owner-leakage.mjs +1011 -0
- package/scripts/lib/validate/check-path-utils-canary.mjs +175 -0
- package/scripts/lib/validate/check-peekaboo-driver-canary.mjs +201 -0
- package/scripts/lib/validate/check-pi-package.mjs +110 -0
- package/scripts/lib/validate/check-pi-prompts.mjs +43 -0
- package/scripts/lib/validate/check-playwright-mcp-canary.mjs +154 -0
- package/scripts/lib/validate/check-plugin-json.mjs +96 -0
- package/scripts/lib/validate/check-plugin-monitors.mjs +206 -0
- package/scripts/lib/validate/check-plugin-schema.mjs +137 -0
- package/scripts/lib/validate/check-rules.mjs +143 -0
- package/scripts/lib/validate/check-session-plan-routing.mjs +154 -0
- package/scripts/lib/validate/check-test-fixture-shapes.mjs +280 -0
- package/scripts/lib/validate/check-unicode-safety.mjs +533 -0
- package/scripts/lib/validate/confidential-names.mjs +169 -0
- package/scripts/lib/validate/dead-bridge-corpus.mjs +141 -0
- package/scripts/lib/validate/dead-bridge-detectors.mjs +568 -0
- package/scripts/lib/validate/tier-inference.mjs +100 -0
- package/scripts/lib/validate-vendored-rules.mjs +519 -0
- package/scripts/lib/vault-archive.mjs +404 -0
- package/scripts/lib/vault-backfill/glab.mjs +164 -0
- package/scripts/lib/vault-backfill/manifest.mjs +75 -0
- package/scripts/lib/vault-backfill/template.mjs +130 -0
- package/scripts/lib/vault-consolidate-fs.mjs +331 -0
- package/scripts/lib/vault-migration-rules.mjs +155 -0
- package/scripts/lib/vault-mirror/auto-commit.mjs +203 -0
- package/scripts/lib/vault-mirror/namespace.mjs +152 -0
- package/scripts/lib/vault-mirror/process.mjs +567 -0
- package/scripts/lib/vault-mirror/pseudonym-map.mjs +164 -0
- package/scripts/lib/vault-mirror/render-learnings.mjs +201 -0
- package/scripts/lib/vault-mirror/render-sessions.mjs +367 -0
- package/scripts/lib/vault-mirror/render.mjs +8 -0
- package/scripts/lib/vault-mirror/utils.mjs +217 -0
- package/scripts/lib/vault-relocation-rules.mjs +555 -0
- package/scripts/lib/vault-repo-backfill.mjs +235 -0
- package/scripts/lib/vault-staleness-banner.mjs +142 -0
- package/scripts/lib/vault-status/board-writer.mjs +769 -0
- package/scripts/lib/vault-status/narrative-mirror.mjs +544 -0
- package/scripts/lib/vault-sync-baseline.mjs +152 -0
- package/scripts/lib/wave-context.mjs +29 -0
- package/scripts/lib/wave-executor/pool.mjs +248 -0
- package/scripts/lib/wave-resource-gate.mjs +204 -0
- package/scripts/lib/wave-sizing.mjs +75 -0
- package/scripts/lib/webhook-url.mjs +105 -0
- package/scripts/lib/workspace.mjs +198 -0
- package/scripts/lib/worktree/constants.mjs +35 -0
- package/scripts/lib/worktree/index.mjs +17 -0
- package/scripts/lib/worktree/lifecycle.mjs +287 -0
- package/scripts/lib/worktree/listing.mjs +118 -0
- package/scripts/lib/worktree/meta.mjs +64 -0
- package/scripts/lib/worktree-freshness.mjs +313 -0
- package/scripts/lib/worktree.mjs +15 -0
- package/scripts/lifecycle-sim-v6.mjs +347 -0
- package/scripts/lock-reaper.mjs +185 -0
- package/scripts/mcp-server.sh +241 -0
- package/scripts/measure-policy-cache-effectiveness.mjs +427 -0
- package/scripts/memory-propose.mjs +464 -0
- package/scripts/migrate-cold-start-seed.mjs +404 -0
- package/scripts/migrate-learnings-jsonl.mjs +189 -0
- package/scripts/migrate-legacy-learnings.sh +61 -0
- package/scripts/migrate-sessions-jsonl.mjs +448 -0
- package/scripts/migrate-subagents-jsonl.mjs +196 -0
- package/scripts/migrate-vault-paths.mjs +796 -0
- package/scripts/parse-config.mjs +149 -0
- package/scripts/pi-install.mjs +117 -0
- package/scripts/print-applicable-rules.mjs +247 -0
- package/scripts/promote-vault-strict.mjs +496 -0
- package/scripts/relocate-vault-corpus.mjs +1178 -0
- package/scripts/run-migrate-v2-cross-repo.mjs +385 -0
- package/scripts/run-quality-gate.mjs +216 -0
- package/scripts/spikes/h3-agent-teams/preflight.sh +53 -0
- package/scripts/spikes/h3-agent-teams/run-h3.sh +112 -0
- package/scripts/spikes/h3-agent-teams/setup.sh +137 -0
- package/scripts/spikes/h3-agent-teams/toggle.sh +38 -0
- package/scripts/sweep-expired-learnings.mjs +135 -0
- package/scripts/sync-vault-schema.mjs +376 -0
- package/scripts/tests/fixtures/fetch-baseline/sample-rule.md +8 -0
- package/scripts/tmux-layout.mjs +245 -0
- package/scripts/token-audit.sh +191 -0
- package/scripts/typecheck.mjs +42 -0
- package/scripts/upload-social-preview.mjs +316 -0
- package/scripts/validate-config.mjs +46 -0
- package/scripts/validate-plugin-manifests.mjs +163 -0
- package/scripts/validate-plugin.mjs +264 -0
- package/scripts/validate-wave-scope.mjs +289 -0
- package/scripts/vault-backfill.mjs +404 -0
- package/scripts/vault-consolidate.mjs +596 -0
- package/scripts/vault-integration-watcher.mjs +394 -0
- package/scripts/vault-mirror.mjs +430 -0
- package/skills/_shared/bootstrap-gate.md +111 -0
- package/skills/_shared/config-reading.md +226 -0
- package/skills/_shared/instruction-file-resolution.md +79 -0
- package/skills/_shared/model-selection.md +64 -0
- package/skills/_shared/monitor-patterns.md +300 -0
- package/skills/_shared/parallel-aware-auq.md +121 -0
- package/skills/_shared/parallel-aware-preamble.md +185 -0
- package/skills/_shared/platform-tools.md +96 -0
- package/skills/_shared/state-ownership.md +221 -0
- package/skills/architecture/DEEPENING.md +37 -0
- package/skills/architecture/INTERFACE-DESIGN.md +44 -0
- package/skills/architecture/LANGUAGE.md +53 -0
- package/skills/architecture/SKILL.md +92 -0
- package/skills/autopilot/SKILL.md +419 -0
- package/skills/bootstrap/SKILL.md +592 -0
- package/skills/bootstrap/STATE.md.template +24 -0
- package/skills/bootstrap/_shared-template.md +243 -0
- package/skills/bootstrap/deep-template.md +659 -0
- package/skills/bootstrap/fast-template.md +251 -0
- package/skills/bootstrap/intensity-heuristic.md +80 -0
- package/skills/bootstrap/public-fallback.md +342 -0
- package/skills/bootstrap/standard-template.md +736 -0
- package/skills/bootstrap/templates/agents/project-code-review.md +18 -0
- package/skills/bootstrap/templates/agents/project-discovery.md +18 -0
- package/skills/bootstrap/templates/agents/project-quality-gate.md +18 -0
- package/skills/brainstorm/SKILL.md +268 -0
- package/skills/brainstorm/soul.md +49 -0
- package/skills/claude-md-drift-check/SKILL.md +186 -0
- package/skills/claude-md-drift-check/checker.mjs +1380 -0
- package/skills/claude-md-drift-check/checker.sh +37 -0
- package/skills/claude-md-drift-check/package.json +16 -0
- package/skills/convergence-monitoring/README.md +39 -0
- package/skills/convergence-monitoring/SIGNALS.md +246 -0
- package/skills/convergence-monitoring/SKILL.md +285 -0
- package/skills/daily/SKILL.md +222 -0
- package/skills/daily/generate.sh +92 -0
- package/skills/daily/templates/daily.md.tpl +36 -0
- package/skills/debug/SKILL.md +188 -0
- package/skills/debug/soul.md +35 -0
- package/skills/discovery/SKILL.md +567 -0
- package/skills/discovery/issue-templates.md +237 -0
- package/skills/discovery/probes/docs-staleness.mjs +195 -0
- package/skills/discovery/probes/frontend-slop.mjs +186 -0
- package/skills/discovery/probes/ssot-code-diff.mjs +310 -0
- package/skills/discovery/probes/supply-chain-slopcheck.mjs +440 -0
- package/skills/discovery/probes/vault-narrative-staleness.mjs +355 -0
- package/skills/discovery/probes/vault-staleness.mjs +272 -0
- package/skills/discovery/probes-arch.md +252 -0
- package/skills/discovery/probes-audit.md +95 -0
- package/skills/discovery/probes-code.md +329 -0
- package/skills/discovery/probes-docs.md +76 -0
- package/skills/discovery/probes-feature.md +150 -0
- package/skills/discovery/probes-infra.md +138 -0
- package/skills/discovery/probes-intro.md +25 -0
- package/skills/discovery/probes-session.md +495 -0
- package/skills/discovery/probes-supply-chain.md +94 -0
- package/skills/discovery/probes-ui.md +147 -0
- package/skills/discovery/probes-vault.md +64 -0
- package/skills/discovery/slop-patterns.md +115 -0
- package/skills/dispatcher/SKILL.md +173 -0
- package/skills/docs-orchestrator/SKILL.md +362 -0
- package/skills/docs-orchestrator/audience-mapping.md +140 -0
- package/skills/domain-model/ADR-FORMAT.md +47 -0
- package/skills/domain-model/CONTEXT-FORMAT.md +77 -0
- package/skills/domain-model/SKILL.md +85 -0
- package/skills/ecosystem-health/SKILL.md +119 -0
- package/skills/ecosystem-health/wizard.md +193 -0
- package/skills/eval/SKILL.md +293 -0
- package/skills/eval/rubric-v1.md +218 -0
- package/skills/evolve/SKILL.md +546 -0
- package/skills/frontmatter-guard/SKILL.md +126 -0
- package/skills/gitlab-ops/SKILL.md +368 -0
- package/skills/gitlab-portfolio/SKILL.md +196 -0
- package/skills/grill/SKILL.md +185 -0
- package/skills/grill/soul.md +55 -0
- package/skills/hook-development/SKILL.md +413 -0
- package/skills/mcp-builder/SKILL.md +260 -0
- package/skills/memory-cleanup/SKILL.md +310 -0
- package/skills/mode-selector/SKILL.md +226 -0
- package/skills/peekaboo-driver/SKILL.md +237 -0
- package/skills/peekaboo-driver/soul.md +32 -0
- package/skills/persona-panel/SKILL.md +365 -0
- package/skills/persona-panel/persona-format.md +205 -0
- package/skills/persona-panel/presets/designer-lens.md +87 -0
- package/skills/persona-panel/presets/engineer-lens.md +88 -0
- package/skills/persona-panel/presets/pm-lens.md +86 -0
- package/skills/plan/SKILL.md +496 -0
- package/skills/plan/mode-feature.md +141 -0
- package/skills/plan/mode-new.md +297 -0
- package/skills/plan/mode-retro.md +271 -0
- package/skills/plan/prd-feature-template.md +132 -0
- package/skills/plan/prd-full-template.md +151 -0
- package/skills/plan/prd-reviewer-prompt.md +103 -0
- package/skills/plan/retro-template.md +75 -0
- package/skills/plan/soul.md +62 -0
- package/skills/playwright-driver/SKILL.md +226 -0
- package/skills/playwright-driver/soul.md +30 -0
- package/skills/quality-gates/SKILL.md +212 -0
- package/skills/reconcile/SKILL.md +324 -0
- package/skills/repo-audit/SKILL.md +272 -0
- package/skills/session-end/SKILL.md +1044 -0
- package/skills/session-end/discovery-scan.md +37 -0
- package/skills/session-end/drift-operations.md +97 -0
- package/skills/session-end/learning-patterns.md +78 -0
- package/skills/session-end/metrics-collection.md +175 -0
- package/skills/session-end/phase-3-2-docs-verification.md +148 -0
- package/skills/session-end/phase-3-6-tail.md +344 -0
- package/skills/session-end/phase-3-7a-recommendations.md +86 -0
- package/skills/session-end/plan-verification.md +288 -0
- package/skills/session-end/session-metrics-write.md +223 -0
- package/skills/session-end/vault-operations.md +50 -0
- package/skills/session-end/verification-checklist.md +20 -0
- package/skills/session-plan/SKILL.md +554 -0
- package/skills/session-plan/wave-template.md +37 -0
- package/skills/session-start/SKILL.md +1043 -0
- package/skills/session-start/phase-2-5-docs-planning.md +119 -0
- package/skills/session-start/phase-4-5-resource-health.md +49 -0
- package/skills/session-start/phase-7-1-premise-check.md +47 -0
- package/skills/session-start/phase-7-5-mode-selector.md +237 -0
- package/skills/session-start/phase-8-5-express-path.md +61 -0
- package/skills/session-start/presentation-format.md +81 -0
- package/skills/session-start/soul.md +57 -0
- package/skills/skill-creator/SKILL.md +168 -0
- package/skills/spinout/SKILL.md +76 -0
- package/skills/sunset-review/SKILL.md +96 -0
- package/skills/test-runner/SKILL.md +362 -0
- package/skills/test-runner/rubric-v1.md +388 -0
- package/skills/test-runner/soul.md +46 -0
- package/skills/tmux-layout/SKILL.md +104 -0
- package/skills/ubiquitous-language/SKILL.md +97 -0
- package/skills/using-orchestrator/SKILL.md +144 -0
- package/skills/vault-mirror/SKILL.md +234 -0
- package/skills/vault-sync/SKILL.md +319 -0
- package/skills/vault-sync/package-lock.json +40 -0
- package/skills/vault-sync/package.json +11 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/90-archive/bad-archived.md +8 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/archive-test-vault/live-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/bad-type.md +8 -0
- package/skills/vault-sync/tests/fixtures/broken-frontmatter-vault/good-note.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/.obsidian/config.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/01-projects/foo/projects-baseline.md +10 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/03-daily/daily-2026-04-13.md +8 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/clean-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/has-dangling.md +9 -0
- package/skills/vault-sync/tests/fixtures/dangling-link-vault/real-target.md +8 -0
- package/skills/vault-sync/tests/fixtures/empty-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/missing-field-vault/missing-id.md +7 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/03-daily/daily-2026-04-13.md +9 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/nested-tag-vault/nested-tags-note.md +11 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/README.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_MOC.md +3 -0
- package/skills/vault-sync/tests/fixtures/no-frontmatter-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_MOC.md +11 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/_meta/.gitkeep +0 -0
- package/skills/vault-sync/tests/fixtures/with-moc-vault/hello-world.md +11 -0
- package/skills/vault-sync/tests/schema-drift.test.mjs +133 -0
- package/skills/vault-sync/validator.mjs +658 -0
- package/skills/vault-sync/validator.sh +55 -0
- package/skills/wave-executor/SKILL.md +496 -0
- package/skills/wave-executor/circuit-breaker.md +169 -0
- package/skills/wave-executor/wave-loop.md +1043 -0
- package/skills/write-executable-plan/SKILL.md +237 -0
- package/skills/write-executable-plan/plan-template.md +154 -0
- package/templates/_minimal/CLAUDE.md.tmpl +41 -0
- package/templates/_minimal/README.md.tmpl +15 -0
- package/templates/_minimal/gitignore.tmpl +47 -0
- package/templates/_shared/harte-regeln.md +16 -0
- package/templates/_shared/loop.md +90 -0
- package/templates/_shared/rules/parallel-sessions.md +77 -0
- package/templates/nextjs-minimal/README.md +30 -0
- package/templates/nextjs-minimal/app/layout.tsx +18 -0
- package/templates/nextjs-minimal/app/page.tsx +7 -0
- package/templates/nextjs-minimal/eslint.config.mjs +16 -0
- package/templates/nextjs-minimal/next.config.mjs +4 -0
- package/templates/nextjs-minimal/package.json +27 -0
- package/templates/nextjs-minimal/tsconfig.json +23 -0
- package/templates/node-minimal/README.md +33 -0
- package/templates/node-minimal/eslint.config.mjs +10 -0
- package/templates/node-minimal/package.json +21 -0
- package/templates/node-minimal/src/index.ts +1 -0
- package/templates/node-minimal/tests/sanity.test.ts +5 -0
- package/templates/node-minimal/tsconfig.json +17 -0
- package/templates/personas/README.md +150 -0
- package/templates/personas/accounting-compliance.v1.md +120 -0
- package/templates/personas/accounting-tax-advisor.v1.md +116 -0
- package/templates/personas/buyer-p1-cto.v1.md +125 -0
- package/templates/personas/buyer-p2-kanzlei.v1.md +134 -0
- package/templates/personas/buyer-p3-build.v1.md +130 -0
- package/templates/personas/buyer-p4-tech-veto.v1.md +130 -0
- package/templates/personas/buyer-p5-solo.v1.md +132 -0
- package/templates/personas/buyer-p6-ld.v1.md +130 -0
- package/templates/personas/klima-ai-expert.v1.md +114 -0
- package/templates/personas/klima-physicist.v1.md +117 -0
- package/templates/python-uv/README.md +28 -0
- package/templates/python-uv/pyproject.toml +32 -0
- package/templates/python-uv/src/__PROJECT_NAME__/__init__.py +0 -0
- package/templates/python-uv/src/__PROJECT_NAME__/main.py +6 -0
- package/templates/python-uv/tests/test_sanity.py +2 -0
- package/templates/static-html/README.md +19 -0
- package/templates/static-html/index.html +15 -0
- package/templates/static-html/script.js +1 -0
- package/templates/static-html/styles.css +26 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-manager detection for Node.js repos (issue #183).
|
|
3
|
+
*
|
|
4
|
+
* Consumed by bootstrap when generating .orchestrator/policy/quality-gates.json
|
|
5
|
+
* to pick sensible default test/typecheck/lint commands.
|
|
6
|
+
*
|
|
7
|
+
* Detection is synchronous. An explicit package.json packageManager field wins;
|
|
8
|
+
* otherwise, root lockfiles are considered when they are not explicitly ignored
|
|
9
|
+
* by the repo's root .gitignore.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
|
|
15
|
+
const PACKAGE_MANAGERS = new Set(['pnpm', 'yarn', 'bun', 'npm']);
|
|
16
|
+
|
|
17
|
+
const LOCKFILES = [
|
|
18
|
+
['pnpm-lock.yaml', 'pnpm'],
|
|
19
|
+
['yarn.lock', 'yarn'],
|
|
20
|
+
['bun.lockb', 'bun'],
|
|
21
|
+
['package-lock.json', 'npm'],
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function packageManagerFromPackageJson(repoRoot) {
|
|
25
|
+
const path = join(repoRoot, 'package.json');
|
|
26
|
+
if (!existsSync(path)) return null;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
30
|
+
const value = parsed.packageManager;
|
|
31
|
+
if (typeof value !== 'string') return null;
|
|
32
|
+
|
|
33
|
+
const name = value.split('@')[0];
|
|
34
|
+
return PACKAGE_MANAGERS.has(name) ? name : null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function escapeRegExp(value) {
|
|
41
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function gitignorePatternMatchesRootFile(pattern, fileName) {
|
|
45
|
+
let normalized = pattern.trim();
|
|
46
|
+
if (!normalized || normalized.startsWith('#') || normalized.endsWith('/')) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (normalized.startsWith('/')) {
|
|
51
|
+
normalized = normalized.slice(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (normalized.includes('/')) {
|
|
55
|
+
return normalized === fileName;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (normalized.includes('*') || normalized.includes('?')) {
|
|
59
|
+
const source = normalized
|
|
60
|
+
.split('*')
|
|
61
|
+
.map((part) => part.split('?').map(escapeRegExp).join('.'))
|
|
62
|
+
.join('.*');
|
|
63
|
+
return new RegExp(`^${source}$`).test(fileName);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return normalized === fileName;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns true when `fileName` sits at the repo root AND is matched
|
|
71
|
+
* (non-negated) by a pattern in the repo's root `.gitignore`.
|
|
72
|
+
*
|
|
73
|
+
* Shared by sibling package-manager detectors (ecosystem-wizard's
|
|
74
|
+
* detectPackageManagerFromRoot, quality-gates-cache's computeDependencyHash)
|
|
75
|
+
* so a gitignored root lockfile is treated as absent everywhere, not just
|
|
76
|
+
* in detectPackageManager() below (issue #715 bug class).
|
|
77
|
+
*
|
|
78
|
+
* @param {string} repoRoot - absolute path to the repo root
|
|
79
|
+
* @param {string} fileName - root-level file name to test (e.g. "pnpm-lock.yaml")
|
|
80
|
+
* @returns {boolean}
|
|
81
|
+
*/
|
|
82
|
+
export function isIgnoredRootFile(repoRoot, fileName) {
|
|
83
|
+
const path = join(repoRoot, '.gitignore');
|
|
84
|
+
if (!existsSync(path)) return false;
|
|
85
|
+
|
|
86
|
+
let ignored = false;
|
|
87
|
+
try {
|
|
88
|
+
const lines = readFileSync(path, 'utf8').split(/\r?\n/);
|
|
89
|
+
for (const rawLine of lines) {
|
|
90
|
+
const line = rawLine.trim();
|
|
91
|
+
if (!line || line.startsWith('#')) continue;
|
|
92
|
+
|
|
93
|
+
const negated = line.startsWith('!');
|
|
94
|
+
const pattern = negated ? line.slice(1) : line;
|
|
95
|
+
if (gitignorePatternMatchesRootFile(pattern, fileName)) {
|
|
96
|
+
ignored = !negated;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return ignored;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Detects the package manager used in the given repo.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} repoRoot - absolute path to the repo root
|
|
110
|
+
* @returns {"pnpm"|"yarn"|"bun"|"npm"|null}
|
|
111
|
+
*/
|
|
112
|
+
export function detectPackageManager(repoRoot) {
|
|
113
|
+
const explicit = packageManagerFromPackageJson(repoRoot);
|
|
114
|
+
if (explicit) return explicit;
|
|
115
|
+
|
|
116
|
+
for (const [lockfile, packageManager] of LOCKFILES) {
|
|
117
|
+
if (
|
|
118
|
+
existsSync(join(repoRoot, lockfile)) &&
|
|
119
|
+
!isIgnoredRootFile(repoRoot, lockfile)
|
|
120
|
+
) {
|
|
121
|
+
return packageManager;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const COMMANDS = {
|
|
129
|
+
pnpm: {
|
|
130
|
+
test: { command: 'pnpm test --run', required: true },
|
|
131
|
+
typecheck: { command: 'pnpm typecheck', required: true },
|
|
132
|
+
lint: { command: 'pnpm lint', required: true },
|
|
133
|
+
},
|
|
134
|
+
npm: {
|
|
135
|
+
test: { command: 'npm test', required: true },
|
|
136
|
+
typecheck: { command: 'npm run typecheck', required: true },
|
|
137
|
+
lint: { command: 'npm run lint', required: true },
|
|
138
|
+
},
|
|
139
|
+
yarn: {
|
|
140
|
+
test: { command: 'yarn test', required: true },
|
|
141
|
+
typecheck: { command: 'yarn typecheck', required: true },
|
|
142
|
+
lint: { command: 'yarn lint', required: true },
|
|
143
|
+
},
|
|
144
|
+
bun: {
|
|
145
|
+
test: { command: 'bun test', required: true },
|
|
146
|
+
typecheck: { command: 'bun run typecheck', required: true },
|
|
147
|
+
lint: { command: 'bun run lint', required: true },
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Returns the default quality-gate commands for a package manager.
|
|
153
|
+
* Unknown / null → npm defaults (most portable).
|
|
154
|
+
*
|
|
155
|
+
* @param {"pnpm"|"yarn"|"bun"|"npm"|null|undefined} packageManager
|
|
156
|
+
* @returns {{test: {command: string, required: boolean}, typecheck: {command: string, required: boolean}, lint: {command: string, required: boolean}}}
|
|
157
|
+
*/
|
|
158
|
+
export function defaultQualityGateCommands(packageManager) {
|
|
159
|
+
return COMMANDS[packageManager] || COMMANDS.npm;
|
|
160
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* path-utils.mjs — Path-traversal-safe helpers for cross-platform use.
|
|
3
|
+
*
|
|
4
|
+
* SECURITY-CRITICAL: Backs enforce-scope.mjs (CWE-23 protection).
|
|
5
|
+
*
|
|
6
|
+
* Most functions are pure (no filesystem I/O). The exception is
|
|
7
|
+
* validatePathInsideProject, which performs a two-phase lexical + realpath
|
|
8
|
+
* guard and calls realpathSync on Phase 2. All other exported helpers remain
|
|
9
|
+
* pure and do not follow symlinks.
|
|
10
|
+
*
|
|
11
|
+
* Part of v3.0.0 migration (Epic #124, issue #130).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { realpathSync } from 'node:fs';
|
|
16
|
+
|
|
17
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Documented attack patterns covered by isPathInside.
|
|
21
|
+
* Exported for test self-check (Wave 4) and audit documentation.
|
|
22
|
+
*/
|
|
23
|
+
export const CWE_23_ATTACK_PATTERNS = [
|
|
24
|
+
'relative-escape', // ../
|
|
25
|
+
'deep-relative-escape', // ../../
|
|
26
|
+
'absolute-escape', // /etc/passwd when parent is /home/user
|
|
27
|
+
'prefix-match-confusion', // /home/userx vs /home/user (not a descendant)
|
|
28
|
+
'windows-drive-escape', // C:\..\Windows
|
|
29
|
+
'case-mismatch', // Windows Foo vs foo (locale-aware)
|
|
30
|
+
'cross-drive', // C:\ vs D:\
|
|
31
|
+
'unc-path', // \\server\share (Windows — unconditionally rejected)
|
|
32
|
+
'null-byte-injection', // embedded \x00 (rejected via input guard)
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
function _assertNonEmptyString(value, fnName, argName) {
|
|
36
|
+
if (typeof value !== 'string' || value.length === 0) {
|
|
37
|
+
throw new TypeError(`${fnName}: ${argName} must be a non-empty string`);
|
|
38
|
+
}
|
|
39
|
+
if (value.includes('\x00')) {
|
|
40
|
+
throw new TypeError(`${fnName}: ${argName} must not contain null bytes`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function _normalizeForCompare(p) {
|
|
45
|
+
// Locale-stable lowercasing on Windows to match filesystem case-insensitivity.
|
|
46
|
+
// toLocaleLowerCase('en-US') avoids Turkish-I style divergence from toLowerCase().
|
|
47
|
+
return IS_WINDOWS ? p.toLocaleLowerCase('en-US') : p;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Returns true if child is strictly inside parent (descendant, not equal).
|
|
52
|
+
*
|
|
53
|
+
* Rejects: relative/absolute escapes, UNC paths on Windows, null bytes, empty inputs.
|
|
54
|
+
* Case-insensitive on Windows (locale-stable).
|
|
55
|
+
*
|
|
56
|
+
* @param {string} child — path to test
|
|
57
|
+
* @param {string} parent — containing path
|
|
58
|
+
* @returns {boolean}
|
|
59
|
+
* @throws {TypeError} on invalid input (empty, null-byte, non-string)
|
|
60
|
+
*/
|
|
61
|
+
export function isPathInside(child, parent) {
|
|
62
|
+
_assertNonEmptyString(child, 'isPathInside', 'child');
|
|
63
|
+
_assertNonEmptyString(parent, 'isPathInside', 'parent');
|
|
64
|
+
|
|
65
|
+
// Unconditionally reject UNC paths on Windows as child input.
|
|
66
|
+
if (IS_WINDOWS && (child.startsWith('\\\\') || child.startsWith('//'))) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const resolvedParent = path.resolve(parent);
|
|
71
|
+
const resolvedChild = path.resolve(child);
|
|
72
|
+
const pa = _normalizeForCompare(resolvedParent);
|
|
73
|
+
const ch = _normalizeForCompare(resolvedChild);
|
|
74
|
+
const rel = path.relative(pa, ch);
|
|
75
|
+
|
|
76
|
+
// Descendant iff: non-empty relative path, no leading '..', and not absolute.
|
|
77
|
+
return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns relative path from root to fullPath.
|
|
82
|
+
*
|
|
83
|
+
* Return contract:
|
|
84
|
+
* - `'.'` → fullPath IS root (same directory)
|
|
85
|
+
* - `'<rel>'` → fullPath is strictly inside root
|
|
86
|
+
* - `null` → fullPath is outside root
|
|
87
|
+
*
|
|
88
|
+
* Callers MUST distinguish `null` from `'.'` — do not use falsy checks.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} root
|
|
91
|
+
* @param {string} fullPath
|
|
92
|
+
* @returns {string|null}
|
|
93
|
+
*/
|
|
94
|
+
export function relativeFromRoot(root, fullPath) {
|
|
95
|
+
_assertNonEmptyString(root, 'relativeFromRoot', 'root');
|
|
96
|
+
_assertNonEmptyString(fullPath, 'relativeFromRoot', 'fullPath');
|
|
97
|
+
|
|
98
|
+
const resolvedRoot = path.resolve(root);
|
|
99
|
+
const resolvedFull = path.resolve(fullPath);
|
|
100
|
+
|
|
101
|
+
if (_normalizeForCompare(resolvedRoot) === _normalizeForCompare(resolvedFull)) {
|
|
102
|
+
return '.';
|
|
103
|
+
}
|
|
104
|
+
if (!isPathInside(fullPath, root)) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return path.relative(resolvedRoot, resolvedFull);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Lowercase on Windows (locale-stable), passthrough on POSIX.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} p
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
export function normalizeCase(p) {
|
|
117
|
+
_assertNonEmptyString(p, 'normalizeCase', 'p');
|
|
118
|
+
return _normalizeForCompare(p);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Returns true if two paths reside on the same drive.
|
|
123
|
+
* On POSIX, always returns true (single-root filesystem).
|
|
124
|
+
*
|
|
125
|
+
* @param {string} a
|
|
126
|
+
* @param {string} b
|
|
127
|
+
* @returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
export function sameDrive(a, b) {
|
|
130
|
+
_assertNonEmptyString(a, 'sameDrive', 'a');
|
|
131
|
+
_assertNonEmptyString(b, 'sameDrive', 'b');
|
|
132
|
+
if (!IS_WINDOWS) return true;
|
|
133
|
+
const rootA = path.parse(path.resolve(a)).root.toLowerCase();
|
|
134
|
+
const rootB = path.parse(path.resolve(b)).root.toLowerCase();
|
|
135
|
+
return rootA === rootB;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Two-phase path-traversal + symlink-escape guard.
|
|
140
|
+
* Phase 1: lexical isPathInside check (rejects ../ traversal).
|
|
141
|
+
* Phase 2: realpath resolution + isPathInside (rejects symlink escape).
|
|
142
|
+
* ENOENT in Phase 2 is swallowed — lexical check is sufficient when path does not yet exist.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} input - User-supplied path (relative or absolute)
|
|
145
|
+
* @param {string} root - Trusted parent directory (absolute)
|
|
146
|
+
* @param {object} [opts] - Optional behaviour modifiers.
|
|
147
|
+
* @param {boolean} [opts.canonicalizeRoot=false] - When true and `root` exists on
|
|
148
|
+
* disk, canonicalize it via `realpathSync` BEFORE the Phase 1 lexical check.
|
|
149
|
+
* This is the documented workaround for macOS tmpdirs where `/var/...` is a
|
|
150
|
+
* symlink to `/private/var/...` — without canonicalization the Phase 2
|
|
151
|
+
* realpath check would reject legitimate descendants because the realpath
|
|
152
|
+
* namespace differs from the lexical namespace.
|
|
153
|
+
* DEFAULT IS `false` — the legacy two-phase behaviour is byte-identical when
|
|
154
|
+
* the option is omitted or set to anything other than `true`.
|
|
155
|
+
*
|
|
156
|
+
* ASYMMETRIC OPT-IN (#548 A3) — current callers split as follows:
|
|
157
|
+
* - opt-in (`canonicalizeRoot: true`):
|
|
158
|
+
* memory-proposals/sink.mjs × 3, memory-proposals/store.mjs × 1
|
|
159
|
+
* These run against `mkdtempSync` paths in tests on macOS and require
|
|
160
|
+
* the workaround to function on `/var → /private/var`.
|
|
161
|
+
* - default (`canonicalizeRoot` omitted): run-migrate-v2-cross-repo.mjs,
|
|
162
|
+
* promote-vault-strict.mjs × 2, vault-integration-watcher.mjs,
|
|
163
|
+
* config/test.mjs, playwright-driver/runner.mjs, profiles/schema.mjs,
|
|
164
|
+
* persona-panel/catalog-loader.mjs × 2, gitlab-portfolio/cli.mjs.
|
|
165
|
+
* Flipping the default to `true` is a behaviour change for the 10 default
|
|
166
|
+
* callers (root would be realpath-resolved before the lexical compare); it
|
|
167
|
+
* is tracked separately and intentionally NOT done here. New callers should
|
|
168
|
+
* pass `{ canonicalizeRoot: true }` when they operate against tmpdir roots.
|
|
169
|
+
* @returns {{ok: true, realPath: string|undefined, lexicalPath: string} | {ok: false, reason: 'lexical'|'symlink'|'input', error?: string}}
|
|
170
|
+
*/
|
|
171
|
+
export function validatePathInsideProject(input, root, opts = {}) {
|
|
172
|
+
// 1. Input validation
|
|
173
|
+
if (typeof input !== 'string' || input.length === 0) {
|
|
174
|
+
return { ok: false, reason: 'input', error: 'input must be a non-empty string' };
|
|
175
|
+
}
|
|
176
|
+
if (input.includes('\0')) {
|
|
177
|
+
return { ok: false, reason: 'input', error: 'input contains null byte' };
|
|
178
|
+
}
|
|
179
|
+
// 1b. Optional root canonicalization (#544 M3 — opt-in for callers that
|
|
180
|
+
// operate against mkdtempSync paths on macOS). Strictly gated on
|
|
181
|
+
// opts.canonicalizeRoot === true so unconditional callers see byte-identical
|
|
182
|
+
// legacy behaviour.
|
|
183
|
+
//
|
|
184
|
+
// #548 A5-ENOENT: the previous `existsSync(root) && realpathSync(root)`
|
|
185
|
+
// pattern was a TOCTOU race — root could be deleted between the existsSync
|
|
186
|
+
// check and the realpathSync call. Drop the precheck and rely on the
|
|
187
|
+
// try/catch alone, narrowed to the two expected error codes (ENOENT for
|
|
188
|
+
// racy tmp-dir teardown, EACCES for unreadable paths). Other errors
|
|
189
|
+
// surface so genuine filesystem faults are not silently swallowed.
|
|
190
|
+
let effectiveRoot = root;
|
|
191
|
+
if (opts && opts.canonicalizeRoot === true) {
|
|
192
|
+
try {
|
|
193
|
+
effectiveRoot = realpathSync(root);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (err && err.code !== 'ENOENT' && err.code !== 'EACCES') throw err;
|
|
196
|
+
// Best-effort: fall back to lexical root if realpath fails with the
|
|
197
|
+
// expected codes. ENOENT/EACCES are normal during tmp-dir tests and
|
|
198
|
+
// racy filesystem operations.
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// 2. Phase 1: lexical
|
|
202
|
+
const lexicalPath = path.resolve(effectiveRoot, input);
|
|
203
|
+
if (!isPathInside(lexicalPath, effectiveRoot)) {
|
|
204
|
+
return { ok: false, reason: 'lexical' };
|
|
205
|
+
}
|
|
206
|
+
// 3. Phase 2: realpath (ENOENT swallowed; other errors propagate — SEC-Q2-LOW-1)
|
|
207
|
+
try {
|
|
208
|
+
const realPath = realpathSync(lexicalPath);
|
|
209
|
+
if (!isPathInside(realPath, effectiveRoot)) {
|
|
210
|
+
return { ok: false, reason: 'symlink' };
|
|
211
|
+
}
|
|
212
|
+
return { ok: true, realPath, lexicalPath };
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (err && err.code !== 'ENOENT') throw err;
|
|
215
|
+
return { ok: true, realPath: undefined, lexicalPath };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* merger.mjs — Sentinel-region three-way merge for peer cards (USER.md / AGENT.md)
|
|
3
|
+
* for issue #503 (Wave 2 I8).
|
|
4
|
+
*
|
|
5
|
+
* Consumers:
|
|
6
|
+
* - /evolve --dialectic pass (future #506)
|
|
7
|
+
*
|
|
8
|
+
* Design notes
|
|
9
|
+
* ────────────
|
|
10
|
+
* • SENTINEL-REGION MERGE (Strategy A per #503 D3). Managed regions are delimited by
|
|
11
|
+
* HTML comment sentinels:
|
|
12
|
+
* <!-- BEGIN MANAGED: <section-name> -->
|
|
13
|
+
* ...AI-replaceable content...
|
|
14
|
+
* <!-- END MANAGED: <section-name> -->
|
|
15
|
+
* Content outside sentinels is hand-owned and preserved verbatim. Content inside
|
|
16
|
+
* sentinels is AI-replaceable.
|
|
17
|
+
* • Pure functions — no fs imports, no IO, deterministic. Same input → same output.
|
|
18
|
+
* • Conflict-surfacing — duplicate section names and orphan BEGIN sentinels become
|
|
19
|
+
* `conflicts[]` entries. The caller decides UX (warn vs prompt vs reject).
|
|
20
|
+
* • Idempotent — `mergePeerCard(body, {})` returns body byte-equivalent (modulo the
|
|
21
|
+
* serialize round-trip; the sentinel comments are re-emitted in the same form).
|
|
22
|
+
*
|
|
23
|
+
* Section-name grammar: `[A-Za-z0-9_-]+` (word characters + hyphen).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const SECTION_NAME = '[\\w-]+';
|
|
27
|
+
const SENTINEL_BEGIN_RE = new RegExp(`<!--\\s*BEGIN\\s+MANAGED:\\s*(${SECTION_NAME})\\s*-->`, 'g');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {Object} HandSection
|
|
31
|
+
* @property {'hand'} type
|
|
32
|
+
* @property {string} content — verbatim text between/around sentinels (may be empty)
|
|
33
|
+
* @property {number} startIdx — absolute char offset in original body
|
|
34
|
+
* @property {number} endIdx — absolute char offset (exclusive)
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @typedef {Object} ManagedSection
|
|
39
|
+
* @property {'managed'} type
|
|
40
|
+
* @property {string} name — section name from the BEGIN sentinel
|
|
41
|
+
* @property {string} content — inner content (excludes the sentinel comments themselves)
|
|
42
|
+
* @property {number} startIdx — offset of the BEGIN sentinel; -1 for sections appended via merge
|
|
43
|
+
* @property {number} endIdx — offset just past the END sentinel; -1 for appended sections
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {HandSection | ManagedSection} Section
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} Conflict
|
|
52
|
+
* @property {'duplicate-section' | 'orphan-begin'} type
|
|
53
|
+
* @property {string} [name] — section name (for duplicate-section and orphan-begin)
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Parse a peer-card body into hand-owned and AI-managed sections.
|
|
58
|
+
*
|
|
59
|
+
* Behaviour
|
|
60
|
+
* ─────────
|
|
61
|
+
* • Text before the first BEGIN sentinel becomes a `hand` section.
|
|
62
|
+
* • Each well-formed BEGIN..END pair becomes a `managed` section. The `content` field
|
|
63
|
+
* excludes the sentinel comments themselves (so re-serialising wraps them back on).
|
|
64
|
+
* • An orphan BEGIN (no matching END) collapses the BEGIN sentinel and all remaining
|
|
65
|
+
* text into a single `hand` section — defensive: we never silently drop user text.
|
|
66
|
+
* Caller should detect this via the absence of the expected managed section in the
|
|
67
|
+
* parse result, OR via the `mergePeerCard` `conflicts[]` (which checks structure).
|
|
68
|
+
* • Trailing text after the last END becomes a `hand` section.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} body
|
|
71
|
+
* @returns {{ sections: Section[] }}
|
|
72
|
+
*/
|
|
73
|
+
function parseSections(body) {
|
|
74
|
+
if (typeof body !== 'string') {
|
|
75
|
+
throw new Error(`parseSections: body must be string (got ${typeof body}).`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sections = [];
|
|
79
|
+
const text = body;
|
|
80
|
+
let cursor = 0;
|
|
81
|
+
|
|
82
|
+
// Reset regex state (global regex carries lastIndex across calls)
|
|
83
|
+
SENTINEL_BEGIN_RE.lastIndex = 0;
|
|
84
|
+
const beginMatches = [...text.matchAll(SENTINEL_BEGIN_RE)];
|
|
85
|
+
|
|
86
|
+
for (const beginMatch of beginMatches) {
|
|
87
|
+
const sectionName = beginMatch[1];
|
|
88
|
+
const beginStart = beginMatch.index;
|
|
89
|
+
|
|
90
|
+
// If a previous orphan-BEGIN already consumed to EOF, skip remaining matches
|
|
91
|
+
if (beginStart < cursor) continue;
|
|
92
|
+
|
|
93
|
+
const beginEnd = beginStart + beginMatch[0].length;
|
|
94
|
+
|
|
95
|
+
// Hand region BEFORE this BEGIN
|
|
96
|
+
if (cursor < beginStart) {
|
|
97
|
+
sections.push({
|
|
98
|
+
type: 'hand',
|
|
99
|
+
content: text.slice(cursor, beginStart),
|
|
100
|
+
startIdx: cursor,
|
|
101
|
+
endIdx: beginStart,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Find matching END (constructed per-section so names match exactly)
|
|
106
|
+
const endRegex = new RegExp(`<!--\\s*END\\s+MANAGED:\\s*${escapeRegex(sectionName)}\\s*-->`);
|
|
107
|
+
const endRest = text.slice(beginEnd);
|
|
108
|
+
const endMatch = endRest.match(endRegex);
|
|
109
|
+
|
|
110
|
+
if (!endMatch) {
|
|
111
|
+
// Orphan BEGIN — defensive: treat the BEGIN sentinel + rest of file as hand
|
|
112
|
+
sections.push({
|
|
113
|
+
type: 'hand',
|
|
114
|
+
content: text.slice(beginStart),
|
|
115
|
+
startIdx: beginStart,
|
|
116
|
+
endIdx: text.length,
|
|
117
|
+
});
|
|
118
|
+
cursor = text.length;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const endStart = beginEnd + endMatch.index;
|
|
123
|
+
const endEnd = endStart + endMatch[0].length;
|
|
124
|
+
|
|
125
|
+
sections.push({
|
|
126
|
+
type: 'managed',
|
|
127
|
+
name: sectionName,
|
|
128
|
+
content: text.slice(beginEnd, endStart),
|
|
129
|
+
startIdx: beginStart,
|
|
130
|
+
endIdx: endEnd,
|
|
131
|
+
});
|
|
132
|
+
cursor = endEnd;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Trailing hand region
|
|
136
|
+
if (cursor < text.length) {
|
|
137
|
+
sections.push({
|
|
138
|
+
type: 'hand',
|
|
139
|
+
content: text.slice(cursor),
|
|
140
|
+
startIdx: cursor,
|
|
141
|
+
endIdx: text.length,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { sections };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Serialize sections back to a body string, wrapping managed regions in sentinels.
|
|
150
|
+
*
|
|
151
|
+
* @param {Section[]} sections
|
|
152
|
+
* @returns {string}
|
|
153
|
+
*/
|
|
154
|
+
function serializeSections(sections) {
|
|
155
|
+
if (!Array.isArray(sections)) {
|
|
156
|
+
throw new Error(`serializeSections: sections must be array (got ${typeof sections}).`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
let out = '';
|
|
160
|
+
for (const s of sections) {
|
|
161
|
+
if (s.type === 'hand') {
|
|
162
|
+
out += s.content;
|
|
163
|
+
} else if (s.type === 'managed') {
|
|
164
|
+
out += `<!-- BEGIN MANAGED: ${s.name} -->`;
|
|
165
|
+
out += s.content;
|
|
166
|
+
out += `<!-- END MANAGED: ${s.name} -->`;
|
|
167
|
+
} else {
|
|
168
|
+
throw new Error(`serializeSections: unknown section type: ${JSON.stringify(s)}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* @typedef {Object} MergeResult
|
|
176
|
+
* @property {string} body — the merged body
|
|
177
|
+
* @property {Conflict[]} conflicts — duplicate-section / orphan-begin issues for caller review
|
|
178
|
+
* @property {{ preserved: number, replaced: number, appended: number }} stats
|
|
179
|
+
*/
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Merge a dialectic-derived update into an existing peer-card body.
|
|
183
|
+
*
|
|
184
|
+
* Semantics
|
|
185
|
+
* ─────────
|
|
186
|
+
* • Hand-owned sections (outside sentinels): PRESERVED verbatim.
|
|
187
|
+
* • Managed sections matching `managedUpdates` keys: REPLACED with new content.
|
|
188
|
+
* • Managed sections in `managedUpdates` not in existing: APPENDED at end.
|
|
189
|
+
* • Managed sections in existing not in `managedUpdates`: KEPT (no auto-delete).
|
|
190
|
+
* • Idempotency: `mergePeerCard(body, {})` returns a body byte-equivalent to `body`
|
|
191
|
+
* (round-trip through parse/serialize, which preserves sentinel form).
|
|
192
|
+
*
|
|
193
|
+
* Conflicts
|
|
194
|
+
* ─────────
|
|
195
|
+
* • `duplicate-section` — same managed section name appears more than once in the
|
|
196
|
+
* existing body. We replace ALL occurrences with the update content (or keep all
|
|
197
|
+
* as-is if no update) but surface the conflict so the caller can resolve.
|
|
198
|
+
* • `orphan-begin` — BEGIN without matching END. Detected by re-parsing the merged
|
|
199
|
+
* body and comparing sentinel structure.
|
|
200
|
+
*
|
|
201
|
+
* @param {string} existingBody — the on-disk body (with sentinels)
|
|
202
|
+
* @param {Record<string, string>} managedUpdates — `{ <section-name>: <new-content> }`
|
|
203
|
+
* @returns {MergeResult}
|
|
204
|
+
*/
|
|
205
|
+
export function mergePeerCard(existingBody, managedUpdates) {
|
|
206
|
+
if (typeof existingBody !== 'string') {
|
|
207
|
+
throw new Error(`mergePeerCard: existingBody must be string (got ${typeof existingBody}).`);
|
|
208
|
+
}
|
|
209
|
+
if (!managedUpdates || typeof managedUpdates !== 'object' || Array.isArray(managedUpdates)) {
|
|
210
|
+
throw new Error('mergePeerCard: managedUpdates must be a plain object.');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Validate update keys against section-name grammar
|
|
214
|
+
for (const name of Object.keys(managedUpdates)) {
|
|
215
|
+
if (!/^[\w-]+$/.test(name)) {
|
|
216
|
+
throw new Error(`mergePeerCard: invalid section name "${name}" (allowed: [A-Za-z0-9_-]+).`);
|
|
217
|
+
}
|
|
218
|
+
if (typeof managedUpdates[name] !== 'string') {
|
|
219
|
+
throw new Error(`mergePeerCard: managedUpdates["${name}"] must be a string.`);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const { sections } = parseSections(existingBody);
|
|
224
|
+
const conflicts = [];
|
|
225
|
+
const stats = { preserved: 0, replaced: 0, appended: 0 };
|
|
226
|
+
|
|
227
|
+
// Detect orphan-begin: a BEGIN sentinel was found but no matching END (the parser
|
|
228
|
+
// collapsed it into a hand section). We re-scan the original text for BEGIN names
|
|
229
|
+
// whose presence in the parsed managed sections is missing — those are orphans.
|
|
230
|
+
const parsedManagedNames = new Set();
|
|
231
|
+
const seenForDuplicate = new Set();
|
|
232
|
+
for (const s of sections) {
|
|
233
|
+
if (s.type === 'managed') {
|
|
234
|
+
parsedManagedNames.add(s.name);
|
|
235
|
+
if (seenForDuplicate.has(s.name)) {
|
|
236
|
+
conflicts.push({ type: 'duplicate-section', name: s.name });
|
|
237
|
+
} else {
|
|
238
|
+
seenForDuplicate.add(s.name);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
SENTINEL_BEGIN_RE.lastIndex = 0;
|
|
244
|
+
const allBeginNames = [...existingBody.matchAll(SENTINEL_BEGIN_RE)].map(m => m[1]);
|
|
245
|
+
const reportedOrphans = new Set();
|
|
246
|
+
for (const name of allBeginNames) {
|
|
247
|
+
if (!parsedManagedNames.has(name) && !reportedOrphans.has(name)) {
|
|
248
|
+
conflicts.push({ type: 'orphan-begin', name });
|
|
249
|
+
reportedOrphans.add(name);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Apply updates: replace managed sections whose names match, preserve others
|
|
254
|
+
const updatedSections = sections.map(s => {
|
|
255
|
+
if (s.type === 'hand') {
|
|
256
|
+
stats.preserved++;
|
|
257
|
+
return s;
|
|
258
|
+
}
|
|
259
|
+
// s.type === 'managed'
|
|
260
|
+
if (Object.prototype.hasOwnProperty.call(managedUpdates, s.name)) {
|
|
261
|
+
stats.replaced++;
|
|
262
|
+
return { ...s, content: wrapManagedContent(managedUpdates[s.name]) };
|
|
263
|
+
}
|
|
264
|
+
// Managed section not in updates — keep as-is
|
|
265
|
+
return s;
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Append new managed sections from updates that didn't exist in original
|
|
269
|
+
for (const name of Object.keys(managedUpdates)) {
|
|
270
|
+
if (!parsedManagedNames.has(name)) {
|
|
271
|
+
stats.appended++;
|
|
272
|
+
updatedSections.push({
|
|
273
|
+
type: 'managed',
|
|
274
|
+
name,
|
|
275
|
+
content: wrapManagedContent(managedUpdates[name]),
|
|
276
|
+
startIdx: -1,
|
|
277
|
+
endIdx: -1,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
body: serializeSections(updatedSections),
|
|
284
|
+
conflicts,
|
|
285
|
+
stats,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// ── helpers ──────────────────────────────────────────────────────────────────
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Normalise managed content so the serialised form always reads:
|
|
293
|
+
* <!-- BEGIN MANAGED: x -->\n<content>\n<!-- END MANAGED: x -->
|
|
294
|
+
* Trimming + adding newlines keeps managed regions visually distinct from sentinels.
|
|
295
|
+
* @param {string} raw
|
|
296
|
+
* @returns {string}
|
|
297
|
+
*/
|
|
298
|
+
function wrapManagedContent(raw) {
|
|
299
|
+
return '\n' + raw.trim() + '\n';
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Escape regex metacharacters in a section name. The grammar `[\w-]+` excludes most
|
|
304
|
+
* metacharacters, but defence-in-depth is cheap here.
|
|
305
|
+
* @param {string} s
|
|
306
|
+
* @returns {string}
|
|
307
|
+
*/
|
|
308
|
+
function escapeRegex(s) {
|
|
309
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
310
|
+
}
|