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,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reader.mjs — Read peer cards (USER.md / AGENT.md) from `.orchestrator/peers/`
|
|
3
|
+
* for issue #503 (Wave 2 I6).
|
|
4
|
+
*
|
|
5
|
+
* Consumers:
|
|
6
|
+
* - session-start Phase 4 staleness banner (Wave 3)
|
|
7
|
+
* - /evolve --dialectic (future #506 — out of scope here)
|
|
8
|
+
*
|
|
9
|
+
* Design notes
|
|
10
|
+
* ────────────
|
|
11
|
+
* • Reuses the canonical YAML-subset parser at `scripts/lib/state-md/yaml-parser.mjs`
|
|
12
|
+
* (`parseStateMd`). It returns `{ frontmatter, body }`, handles flow arrays
|
|
13
|
+
* (`[a, b, c]`), strings, booleans, integers, nulls — the same superset peer-cards
|
|
14
|
+
* need. "STATE.md" in the function name is incidental; the grammar is just
|
|
15
|
+
* "markdown with `---` YAML frontmatter".
|
|
16
|
+
* • Schema validation is delegated to `./schema.mjs` (I5). This module never
|
|
17
|
+
* decides what a valid peer card looks like; it just hands the parsed
|
|
18
|
+
* frontmatter to the validator and reports the verdict.
|
|
19
|
+
* • Read-only by contract. Never writes, never throws on missing files /
|
|
20
|
+
* malformed frontmatter — graceful degradation lets the banner caller
|
|
21
|
+
* decide UX (warn vs. silent).
|
|
22
|
+
* • Clock is injectable (`opts.now`) so staleness tests are deterministic.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { readFile } from 'node:fs/promises';
|
|
26
|
+
import { existsSync } from 'node:fs';
|
|
27
|
+
import { join } from 'node:path';
|
|
28
|
+
|
|
29
|
+
import { parseStateMd } from '../state-md/yaml-parser.mjs';
|
|
30
|
+
import {
|
|
31
|
+
validatePeerCardFrontmatter,
|
|
32
|
+
computeStalenessDays,
|
|
33
|
+
STALENESS_THRESHOLD_DAYS,
|
|
34
|
+
} from './schema.mjs';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @typedef {Object} PeerCard
|
|
38
|
+
* @property {Record<string, unknown> | null} frontmatter — parsed YAML frontmatter, or null when missing/malformed
|
|
39
|
+
* @property {string} body — markdown body after the closing `---` (empty string if none)
|
|
40
|
+
* @property {number} stalenessDays — whole days since `frontmatter.updated`; `Infinity` if no `updated` field
|
|
41
|
+
* @property {boolean} isStale — `stalenessDays > STALENESS_THRESHOLD_DAYS`
|
|
42
|
+
* @property {{ ok: boolean, errors: string[] }} validation — schema verdict from `validatePeerCardFrontmatter`
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {Object} PeerCardsResult
|
|
47
|
+
* @property {PeerCard | null} user — USER.md card; null if file missing
|
|
48
|
+
* @property {PeerCard | null} agent — AGENT.md card; null if file missing
|
|
49
|
+
* @property {string} peersDir — absolute path to `.orchestrator/peers/`
|
|
50
|
+
* @property {boolean} exists — whether peersDir exists on disk
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Read peer cards from `<repoRoot>/.orchestrator/peers/`.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} repoRoot — REQUIRED, absolute path to repo root
|
|
57
|
+
* @param {{ now?: Date }} [opts]
|
|
58
|
+
* @returns {Promise<PeerCardsResult>}
|
|
59
|
+
*/
|
|
60
|
+
export async function readPeerCards(repoRoot, opts = {}) {
|
|
61
|
+
if (!repoRoot || typeof repoRoot !== 'string') {
|
|
62
|
+
throw new Error(`readPeerCards: repoRoot is required (got ${typeof repoRoot}).`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const peersDir = join(repoRoot, '.orchestrator', 'peers');
|
|
66
|
+
|
|
67
|
+
if (!existsSync(peersDir)) {
|
|
68
|
+
return { user: null, agent: null, peersDir, exists: false };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const now = opts.now ?? new Date();
|
|
72
|
+
const userPath = join(peersDir, 'USER.md');
|
|
73
|
+
const agentPath = join(peersDir, 'AGENT.md');
|
|
74
|
+
|
|
75
|
+
const [user, agent] = await Promise.all([
|
|
76
|
+
readOneCard(userPath, now),
|
|
77
|
+
readOneCard(agentPath, now),
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
return { user, agent, peersDir, exists: true };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Read and parse a single peer-card file. Returns null if the file is missing.
|
|
85
|
+
* Never throws — degrades to a card with `validation.ok === false`.
|
|
86
|
+
*
|
|
87
|
+
* @param {string} absPath
|
|
88
|
+
* @param {Date} now
|
|
89
|
+
* @returns {Promise<PeerCard | null>}
|
|
90
|
+
*/
|
|
91
|
+
async function readOneCard(absPath, now) {
|
|
92
|
+
if (!existsSync(absPath)) return null;
|
|
93
|
+
|
|
94
|
+
let content;
|
|
95
|
+
try {
|
|
96
|
+
content = await readFile(absPath, 'utf8');
|
|
97
|
+
} catch (err) {
|
|
98
|
+
return {
|
|
99
|
+
frontmatter: null,
|
|
100
|
+
body: '',
|
|
101
|
+
stalenessDays: Infinity,
|
|
102
|
+
isStale: true,
|
|
103
|
+
validation: { ok: false, errors: [`read failed: ${err.message}`] },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parsed = parseStateMd(content);
|
|
108
|
+
if (parsed === null) {
|
|
109
|
+
return {
|
|
110
|
+
frontmatter: null,
|
|
111
|
+
body: content,
|
|
112
|
+
stalenessDays: Infinity,
|
|
113
|
+
isStale: true,
|
|
114
|
+
validation: { ok: false, errors: ['no frontmatter or malformed YAML'] },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const { frontmatter, body } = parsed;
|
|
119
|
+
const validation = validatePeerCardFrontmatter(frontmatter);
|
|
120
|
+
const updatedIso = typeof frontmatter.updated === 'string' ? frontmatter.updated : null;
|
|
121
|
+
const stalenessDays = updatedIso ? computeStalenessDays(updatedIso, now) : Infinity;
|
|
122
|
+
const isStale = stalenessDays > STALENESS_THRESHOLD_DAYS;
|
|
123
|
+
|
|
124
|
+
return { frontmatter, body, stalenessDays, isStale, validation };
|
|
125
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scripts/lib/peer-cards/schema.mjs — Peer-card frontmatter validator (issue #503).
|
|
3
|
+
*
|
|
4
|
+
* Pure ESM, no I/O, no external dependencies. Used by reader.mjs / writer.mjs /
|
|
5
|
+
* merger.mjs (siblings under scripts/lib/peer-cards/) to validate peer-card
|
|
6
|
+
* YAML frontmatter on read and before write.
|
|
7
|
+
*
|
|
8
|
+
* Design notes:
|
|
9
|
+
* - Follows the `mission-status-schema.mjs` convention (pure regex/checks)
|
|
10
|
+
* rather than `skills/vault-sync/validator.mjs` (zod). The repo root has
|
|
11
|
+
* no `zod` dependency — only `skills/vault-sync/` does, scoped to that
|
|
12
|
+
* skill's own `node_modules`. Keeping this validator dependency-free lets
|
|
13
|
+
* callers from anywhere under `scripts/lib/` use it without extra setup.
|
|
14
|
+
* - The shape mirrors the canonical `vaultFrontmatterSchema` in
|
|
15
|
+
* `skills/vault-sync/validator.mjs` (slug regex, ISO date regex, optional
|
|
16
|
+
* title/tags) and extends it with the peer-card specific `target` enum and
|
|
17
|
+
* `source_sessions` array.
|
|
18
|
+
* - The `'peer-card'` discriminator is added to the vault-sync enum via the
|
|
19
|
+
* vendored-edit + sync-script-baseline path (see scripts/sync-vault-schema.mjs
|
|
20
|
+
* and the BEGIN/END GENERATED SCHEMA sentinel block in validator.mjs).
|
|
21
|
+
*
|
|
22
|
+
* Required frontmatter fields (per #503 AC1):
|
|
23
|
+
* id : kebab-case slug, 2..128 chars
|
|
24
|
+
* type : literal 'peer-card'
|
|
25
|
+
* target : 'user' | 'agent'
|
|
26
|
+
* created : ISO-8601 timestamp
|
|
27
|
+
* updated : ISO-8601 timestamp
|
|
28
|
+
* source_sessions : string[] (may be empty; defaults to [] when absent)
|
|
29
|
+
*
|
|
30
|
+
* Optional fields:
|
|
31
|
+
* title : 1..200 chars
|
|
32
|
+
* tags : string[]
|
|
33
|
+
*
|
|
34
|
+
* Unknown fields pass through (mirrors the .passthrough() behaviour of the
|
|
35
|
+
* canonical vault schema).
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
// ── Constants ───────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/** Canonical kebab-case slug regex (mirrors vault-sync slugRegex). */
|
|
41
|
+
const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Strict ISO 8601 datetime regex with UTC Z suffix, matching the AC1 example
|
|
45
|
+
* `2026-05-23T12:30:00Z`. Optional millisecond precision is accepted.
|
|
46
|
+
* Note: the canonical vault schema accepts date-only and offsets too; peer-cards
|
|
47
|
+
* deliberately require the full timestamp form for reproducible ordering.
|
|
48
|
+
*/
|
|
49
|
+
const ISO_DATETIME_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z$/;
|
|
50
|
+
|
|
51
|
+
/** Permitted peer-card target values. */
|
|
52
|
+
export const PEER_CARD_TARGETS = Object.freeze(['user', 'agent']);
|
|
53
|
+
|
|
54
|
+
/** Staleness threshold in days — used by merger / discovery probes. */
|
|
55
|
+
export const STALENESS_THRESHOLD_DAYS = 30;
|
|
56
|
+
|
|
57
|
+
/** Inclusive bounds for the `id` field. */
|
|
58
|
+
const ID_MIN_LEN = 2;
|
|
59
|
+
const ID_MAX_LEN = 128;
|
|
60
|
+
|
|
61
|
+
/** Inclusive bounds for the optional `title` field. */
|
|
62
|
+
const TITLE_MIN_LEN = 1;
|
|
63
|
+
const TITLE_MAX_LEN = 200;
|
|
64
|
+
|
|
65
|
+
// ── Type guards ─────────────────────────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
function _isString(v) {
|
|
68
|
+
return typeof v === 'string';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function _isStringArray(v) {
|
|
72
|
+
return Array.isArray(v) && v.every(_isString);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Public predicates ──────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Returns true when `value` is one of the canonical peer-card target strings.
|
|
79
|
+
*
|
|
80
|
+
* @param {unknown} value
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
83
|
+
function isValidPeerCardTarget(value) {
|
|
84
|
+
return typeof value === 'string' && PEER_CARD_TARGETS.includes(value);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Returns true when `value` is a valid kebab-case slug within the id length
|
|
89
|
+
* bounds (2..128).
|
|
90
|
+
*
|
|
91
|
+
* @param {unknown} value
|
|
92
|
+
* @returns {boolean}
|
|
93
|
+
*/
|
|
94
|
+
function isValidPeerCardId(value) {
|
|
95
|
+
if (typeof value !== 'string') return false;
|
|
96
|
+
if (value.length < ID_MIN_LEN || value.length > ID_MAX_LEN) return false;
|
|
97
|
+
return SLUG_REGEX.test(value);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns true when `value` is a strict ISO 8601 datetime ending in `Z`.
|
|
102
|
+
*
|
|
103
|
+
* @param {unknown} value
|
|
104
|
+
* @returns {boolean}
|
|
105
|
+
*/
|
|
106
|
+
function isValidIsoTimestamp(value) {
|
|
107
|
+
return typeof value === 'string' && ISO_DATETIME_REGEX.test(value);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Validator ──────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validate a parsed peer-card frontmatter object against the #503 AC1 shape.
|
|
114
|
+
*
|
|
115
|
+
* Returns a discriminated result object:
|
|
116
|
+
* - { ok: true, data: object } — input is valid; `data` is the input with
|
|
117
|
+
* `source_sessions` defaulted to `[]` when
|
|
118
|
+
* absent (mirrors AC1 default). Unknown
|
|
119
|
+
* fields are preserved (passthrough).
|
|
120
|
+
* - { ok: false, errors: string[] } — one entry per failed field, formatted
|
|
121
|
+
* as `"<path>: <message>"`.
|
|
122
|
+
*
|
|
123
|
+
* Never throws.
|
|
124
|
+
*
|
|
125
|
+
* @param {unknown} input - parsed YAML frontmatter object
|
|
126
|
+
* @returns {{ ok: true, data: object } | { ok: false, errors: string[] }}
|
|
127
|
+
*/
|
|
128
|
+
export function validatePeerCardFrontmatter(input) {
|
|
129
|
+
const errors = [];
|
|
130
|
+
|
|
131
|
+
if (input === null || typeof input !== 'object' || Array.isArray(input)) {
|
|
132
|
+
return { ok: false, errors: ['<root>: frontmatter must be a plain object'] };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ── id ──────────────────────────────────────────────────────────────────
|
|
136
|
+
if (!('id' in input)) {
|
|
137
|
+
errors.push('id: required field missing');
|
|
138
|
+
} else if (!isValidPeerCardId(input.id)) {
|
|
139
|
+
errors.push(
|
|
140
|
+
`id: must be a kebab-case slug of ${ID_MIN_LEN}..${ID_MAX_LEN} chars (matching ${SLUG_REGEX.source})`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ── type ────────────────────────────────────────────────────────────────
|
|
145
|
+
if (!('type' in input)) {
|
|
146
|
+
errors.push('type: required field missing');
|
|
147
|
+
} else if (input.type !== 'peer-card') {
|
|
148
|
+
errors.push(`type: must be literal "peer-card" (got ${JSON.stringify(input.type)})`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── target ──────────────────────────────────────────────────────────────
|
|
152
|
+
if (!('target' in input)) {
|
|
153
|
+
errors.push('target: required field missing');
|
|
154
|
+
} else if (!isValidPeerCardTarget(input.target)) {
|
|
155
|
+
errors.push(
|
|
156
|
+
`target: must be one of ${JSON.stringify(PEER_CARD_TARGETS)} (got ${JSON.stringify(input.target)})`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// ── created ─────────────────────────────────────────────────────────────
|
|
161
|
+
if (!('created' in input)) {
|
|
162
|
+
errors.push('created: required field missing');
|
|
163
|
+
} else if (!isValidIsoTimestamp(input.created)) {
|
|
164
|
+
errors.push('created: must be an ISO 8601 timestamp ending in Z (e.g. 2026-05-23T12:30:00Z)');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ── updated ─────────────────────────────────────────────────────────────
|
|
168
|
+
if (!('updated' in input)) {
|
|
169
|
+
errors.push('updated: required field missing');
|
|
170
|
+
} else if (!isValidIsoTimestamp(input.updated)) {
|
|
171
|
+
errors.push('updated: must be an ISO 8601 timestamp ending in Z (e.g. 2026-05-23T12:30:00Z)');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── source_sessions (default: []) ───────────────────────────────────────
|
|
175
|
+
let sourceSessions;
|
|
176
|
+
if (!('source_sessions' in input) || input.source_sessions === undefined) {
|
|
177
|
+
sourceSessions = [];
|
|
178
|
+
} else if (!_isStringArray(input.source_sessions)) {
|
|
179
|
+
errors.push('source_sessions: must be an array of strings');
|
|
180
|
+
sourceSessions = undefined;
|
|
181
|
+
} else {
|
|
182
|
+
sourceSessions = input.source_sessions;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// ── Optional: title ────────────────────────────────────────────────────
|
|
186
|
+
if ('title' in input && input.title !== undefined) {
|
|
187
|
+
if (!_isString(input.title)) {
|
|
188
|
+
errors.push('title: must be a string when provided');
|
|
189
|
+
} else if (input.title.length < TITLE_MIN_LEN || input.title.length > TITLE_MAX_LEN) {
|
|
190
|
+
errors.push(`title: length must be ${TITLE_MIN_LEN}..${TITLE_MAX_LEN} chars`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── Optional: tags ─────────────────────────────────────────────────────
|
|
195
|
+
if ('tags' in input && input.tags !== undefined) {
|
|
196
|
+
if (!_isStringArray(input.tags)) {
|
|
197
|
+
errors.push('tags: must be an array of strings when provided');
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (errors.length > 0) {
|
|
202
|
+
return { ok: false, errors };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Spread input first so passthrough fields are preserved, then overwrite
|
|
206
|
+
// source_sessions with the defaulted value.
|
|
207
|
+
return { ok: true, data: { ...input, source_sessions: sourceSessions } };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Staleness helpers ──────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Compute staleness in whole days between `updatedIso` and `now`.
|
|
214
|
+
*
|
|
215
|
+
* Returns `Infinity` when `updatedIso` cannot be parsed by `Date`. The result
|
|
216
|
+
* floors toward zero so a card updated 29 hours ago reads as `1` day stale.
|
|
217
|
+
*
|
|
218
|
+
* @param {string} updatedIso - ISO 8601 timestamp from peer-card frontmatter
|
|
219
|
+
* @param {Date} [now=new Date()] - injectable clock for tests
|
|
220
|
+
* @returns {number} whole days since `updatedIso`; `Infinity` on parse failure
|
|
221
|
+
*/
|
|
222
|
+
export function computeStalenessDays(updatedIso, now = new Date()) {
|
|
223
|
+
if (typeof updatedIso !== 'string') return Infinity;
|
|
224
|
+
const updated = new Date(updatedIso);
|
|
225
|
+
const t = updated.getTime();
|
|
226
|
+
if (Number.isNaN(t)) return Infinity;
|
|
227
|
+
const nowMs = now instanceof Date ? now.getTime() : Number(now);
|
|
228
|
+
if (!Number.isFinite(nowMs)) return Infinity;
|
|
229
|
+
return Math.floor((nowMs - t) / 86_400_000);
|
|
230
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* staleness-banner.mjs — Session-start banner for #503 peer-card staleness.
|
|
3
|
+
*
|
|
4
|
+
* Surfaces a `warn` banner during session-start Phase 4 when peer cards
|
|
5
|
+
* (USER.md / AGENT.md under `.orchestrator/peers/`) have an `updated:`
|
|
6
|
+
* frontmatter older than `STALENESS_THRESHOLD_DAYS` (30) days.
|
|
7
|
+
*
|
|
8
|
+
* Design notes:
|
|
9
|
+
* - Mirrors the contract used by other Phase 4 banners
|
|
10
|
+
* (`scripts/lib/vault-staleness-banner.mjs`, `scripts/lib/ci-status-banner.mjs`,
|
|
11
|
+
* `scripts/lib/qg-command-drift-banner.mjs`): a single `checkXxx()` entry
|
|
12
|
+
* point that returns `null` (silent no-op) or `{ severity, message, ... }`.
|
|
13
|
+
* - Never throws. The banner is informational — the session-start renderer
|
|
14
|
+
* must not be derailed by reader errors, missing files, or schema mismatches.
|
|
15
|
+
* Defensive `try/catch` wraps every potentially-throwing call.
|
|
16
|
+
* - Clock is injectable (`opts.now`) to keep tests deterministic. Propagates
|
|
17
|
+
* through to `readPeerCards()` so staleness computations agree across the
|
|
18
|
+
* reader and the banner.
|
|
19
|
+
*
|
|
20
|
+
* Cross-references:
|
|
21
|
+
* - `scripts/lib/peer-cards/reader.mjs` — produces the `PeerCardsResult`
|
|
22
|
+
* consumed here.
|
|
23
|
+
* - `scripts/lib/peer-cards/schema.mjs` — `STALENESS_THRESHOLD_DAYS = 30`.
|
|
24
|
+
* - `.claude/rules/owner-persona.md` — host-wide `owner.yaml`; peer cards
|
|
25
|
+
* are the per-repo behavioural complement.
|
|
26
|
+
* - `skills/session-start/SKILL.md` Phase 4 — banner render site.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { readPeerCards } from './reader.mjs';
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check peer-card staleness and produce a session-start banner.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} opts
|
|
35
|
+
* @param {string} opts.repoRoot - REQUIRED absolute path to repo root
|
|
36
|
+
* @param {Date} [opts.now] - injectable clock for tests
|
|
37
|
+
* @returns {Promise<null | { severity: 'warn', message: string, stale: Array<{target: string, days: number}> }>}
|
|
38
|
+
*/
|
|
39
|
+
export async function checkPeerCardsStaleness({ repoRoot, now } = {}) {
|
|
40
|
+
// Silent no-op on bad input (consistent with other Phase 4 banners).
|
|
41
|
+
if (!repoRoot || typeof repoRoot !== 'string') return null;
|
|
42
|
+
|
|
43
|
+
let result;
|
|
44
|
+
try {
|
|
45
|
+
result = await readPeerCards(repoRoot, { now });
|
|
46
|
+
} catch {
|
|
47
|
+
// Never throw — banner is informational only.
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!result || !result.exists) return null;
|
|
52
|
+
|
|
53
|
+
const stale = [];
|
|
54
|
+
|
|
55
|
+
// Only count cards that are (a) present, (b) have valid frontmatter so the
|
|
56
|
+
// `updated:` date could be parsed, and (c) flagged stale by the reader.
|
|
57
|
+
// A card without parseable frontmatter still has `isStale: true` because
|
|
58
|
+
// `stalenessDays === Infinity`, but in that case the corrective action is
|
|
59
|
+
// "fix the frontmatter", not "run /evolve --dialectic" — so we exclude it
|
|
60
|
+
// from this banner.
|
|
61
|
+
if (
|
|
62
|
+
result.user &&
|
|
63
|
+
result.user.isStale &&
|
|
64
|
+
result.user.frontmatter &&
|
|
65
|
+
Number.isFinite(result.user.stalenessDays)
|
|
66
|
+
) {
|
|
67
|
+
stale.push({ target: 'USER.md', days: result.user.stalenessDays });
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
result.agent &&
|
|
71
|
+
result.agent.isStale &&
|
|
72
|
+
result.agent.frontmatter &&
|
|
73
|
+
Number.isFinite(result.agent.stalenessDays)
|
|
74
|
+
) {
|
|
75
|
+
stale.push({ target: 'AGENT.md', days: result.agent.stalenessDays });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (stale.length === 0) return null;
|
|
79
|
+
|
|
80
|
+
const parts = stale.map((s) => `${s.target} (${s.days}d)`).join(', ');
|
|
81
|
+
const message =
|
|
82
|
+
`⚠ peer-cards: ${parts} stale (>30 days) — ` +
|
|
83
|
+
`consider running /evolve --dialectic to refresh.`;
|
|
84
|
+
|
|
85
|
+
return { severity: 'warn', message, stale };
|
|
86
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* writer.mjs — Atomic writer for peer-card markdown files.
|
|
3
|
+
*
|
|
4
|
+
* Peer cards live at `.orchestrator/peers/{USER,AGENT}.md` and carry a frontmatter
|
|
5
|
+
* block followed by markdown body. Writes are crash-safe via tmp + rename (POSIX
|
|
6
|
+
* atomic same-filesystem rename), and the writer ENFORCES the EARS unwanted-behaviour
|
|
7
|
+
* rule from #503: a peer-card missing the required `id` field fails with
|
|
8
|
+
* `peer-card missing required field: id` and writes nothing.
|
|
9
|
+
*
|
|
10
|
+
* Full frontmatter validation is delegated to schema.mjs (`validatePeerCardFrontmatter`);
|
|
11
|
+
* if validation fails for any reason (missing fields, wrong types, etc.) the writer
|
|
12
|
+
* returns `{ ok: false, errors }` and leaves the target file untouched.
|
|
13
|
+
*
|
|
14
|
+
* Auto-fill semantics (only when caller omits the field):
|
|
15
|
+
* - type → 'peer-card'
|
|
16
|
+
* - target → from the function argument
|
|
17
|
+
* - updated → current ISO timestamp
|
|
18
|
+
* - created → mirrors `updated` if absent
|
|
19
|
+
* - source_sessions → []
|
|
20
|
+
* - `id` is NEVER auto-filled — callers must provide it (EARS gate).
|
|
21
|
+
*
|
|
22
|
+
* Part of #503 (Wave 2; sibling to schema.mjs / reader.mjs / merger.mjs).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { writeFile, rename, mkdir, unlink } from 'node:fs/promises';
|
|
26
|
+
import { join } from 'node:path';
|
|
27
|
+
import { randomUUID } from 'node:crypto';
|
|
28
|
+
|
|
29
|
+
import { validatePeerCardFrontmatter } from './schema.mjs';
|
|
30
|
+
|
|
31
|
+
// Frontmatter keys serialized in this order. Anything else is appended.
|
|
32
|
+
const ORDERED_KEYS = ['id', 'type', 'target', 'created', 'updated', 'source_sessions', 'title', 'tags'];
|
|
33
|
+
|
|
34
|
+
// YAML special chars that force quoting of scalar string values.
|
|
35
|
+
const YAML_SPECIAL = /[:#&*!|>'"%@`,[\]{}]/;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Serialize a flat frontmatter object to a YAML `---` block.
|
|
39
|
+
*
|
|
40
|
+
* Peer-card frontmatter is intentionally flat (no nested objects per schema), so this
|
|
41
|
+
* helper handles scalars, string arrays, and string-keyed booleans/numbers. Strings
|
|
42
|
+
* containing YAML-special characters are quoted via JSON.stringify (safe superset of
|
|
43
|
+
* YAML double-quoted scalar).
|
|
44
|
+
*
|
|
45
|
+
* @param {Record<string, unknown>} fm
|
|
46
|
+
* @returns {string} YAML block including the opening and closing `---` markers.
|
|
47
|
+
*/
|
|
48
|
+
function serializeFrontmatter(fm) {
|
|
49
|
+
const lines = ['---'];
|
|
50
|
+
|
|
51
|
+
const writeKey = (key, value) => {
|
|
52
|
+
if (Array.isArray(value)) {
|
|
53
|
+
lines.push(`${key}: [${value.map((v) => JSON.stringify(v)).join(', ')}]`);
|
|
54
|
+
} else if (typeof value === 'string') {
|
|
55
|
+
const needsQuoting = YAML_SPECIAL.test(value);
|
|
56
|
+
lines.push(`${key}: ${needsQuoting ? JSON.stringify(value) : value}`);
|
|
57
|
+
} else {
|
|
58
|
+
lines.push(`${key}: ${JSON.stringify(value)}`);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
for (const key of ORDERED_KEYS) {
|
|
63
|
+
if (key in fm) writeKey(key, fm[key]);
|
|
64
|
+
}
|
|
65
|
+
for (const key of Object.keys(fm)) {
|
|
66
|
+
if (!ORDERED_KEYS.includes(key)) writeKey(key, fm[key]);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
lines.push('---');
|
|
70
|
+
return lines.join('\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Atomically write a peer card to `.orchestrator/peers/{USER,AGENT}.md`.
|
|
75
|
+
*
|
|
76
|
+
* @param {string} repoRoot Absolute path to the repo root (required).
|
|
77
|
+
* @param {'user'|'agent'} target Which peer card to write.
|
|
78
|
+
* @param {{ frontmatter: object, body: string }} card Card content.
|
|
79
|
+
* @returns {Promise<{ ok: true, path: string } | { ok: false, errors: string[] }>}
|
|
80
|
+
* @throws {Error} On invalid input (bad repoRoot, bad target, bad card).
|
|
81
|
+
*/
|
|
82
|
+
export async function writePeerCard(repoRoot, target, card) {
|
|
83
|
+
if (!repoRoot || typeof repoRoot !== 'string') {
|
|
84
|
+
throw new Error(`writePeerCard: repoRoot is required (got ${typeof repoRoot}).`);
|
|
85
|
+
}
|
|
86
|
+
if (target !== 'user' && target !== 'agent') {
|
|
87
|
+
throw new Error(`writePeerCard: target must be 'user' or 'agent' (got ${JSON.stringify(target)}).`);
|
|
88
|
+
}
|
|
89
|
+
if (!card || typeof card !== 'object') {
|
|
90
|
+
throw new Error('writePeerCard: card must be an object.');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Build the effective frontmatter with auto-filled derived fields.
|
|
94
|
+
// Note: `id` is INTENTIONALLY not auto-filled — the EARS gate below depends on this.
|
|
95
|
+
const fm = { ...card.frontmatter };
|
|
96
|
+
fm.type = 'peer-card';
|
|
97
|
+
fm.target = target;
|
|
98
|
+
fm.updated = fm.updated || new Date().toISOString();
|
|
99
|
+
fm.created = fm.created || fm.updated;
|
|
100
|
+
fm.source_sessions = fm.source_sessions || [];
|
|
101
|
+
|
|
102
|
+
// EARS unwanted-behaviour gate (#503): refuse to write without id.
|
|
103
|
+
if (!fm.id) {
|
|
104
|
+
return { ok: false, errors: ['peer-card missing required field: id'] };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Full schema validation — schema.mjs is the source of truth for valid shapes.
|
|
108
|
+
const validation = validatePeerCardFrontmatter(fm);
|
|
109
|
+
if (!validation.ok) {
|
|
110
|
+
return { ok: false, errors: validation.errors };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const filename = target === 'user' ? 'USER.md' : 'AGENT.md';
|
|
114
|
+
const peersDir = join(repoRoot, '.orchestrator', 'peers');
|
|
115
|
+
const finalPath = join(peersDir, filename);
|
|
116
|
+
|
|
117
|
+
await mkdir(peersDir, { recursive: true });
|
|
118
|
+
|
|
119
|
+
const bodyStr = typeof card.body === 'string' ? card.body : '';
|
|
120
|
+
const content = `${serializeFrontmatter(fm)}\n\n${bodyStr.trimStart()}`;
|
|
121
|
+
const tmpPath = `${finalPath}.${randomUUID().slice(0, 8)}.tmp`;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
await writeFile(tmpPath, content, 'utf8');
|
|
125
|
+
await rename(tmpPath, finalPath);
|
|
126
|
+
return { ok: true, path: finalPath };
|
|
127
|
+
} catch (err) {
|
|
128
|
+
// Best-effort cleanup of orphaned tmp file. Swallow secondary failures —
|
|
129
|
+
// the primary error is what the caller needs to see.
|
|
130
|
+
try {
|
|
131
|
+
await unlink(tmpPath);
|
|
132
|
+
} catch {
|
|
133
|
+
/* tmp file may not exist if writeFile failed before creating it */
|
|
134
|
+
}
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|