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,544 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* narrative-mirror.mjs — Durable per-repo narrative mirror (Epic #673 Phase 1, #675).
|
|
3
|
+
*
|
|
4
|
+
* At session-end, extract from a repo's `.claude/STATE.md` the DURABLE narrative —
|
|
5
|
+
* `## Wave History`, `## Deviations`, `## What Not To Retry`, plus the mission-status
|
|
6
|
+
* rollup — and idempotently mirror it into a generator-owned per-repo vault file, so a
|
|
7
|
+
* reviewer or stand-in can read PER REPO what was done, what failed, and what not to
|
|
8
|
+
* retry, WITHOUT opening the repo.
|
|
9
|
+
*
|
|
10
|
+
* Design decision (Discovery D3): there is NO Wave-History parser and NO `readDeviations`
|
|
11
|
+
* in scripts/lib/state-md.mjs, and `readWhatNotToRetry`'s expected format DIVERGES from
|
|
12
|
+
* the real FeedFoundryV2 fixture (which uses a plain `- <text> (<session>, <date>) —
|
|
13
|
+
* why: …` bullet). Rather than rely on structured parsers that silently return `[]` on
|
|
14
|
+
* format drift, this module extracts the RAW verbatim markdown block of each section —
|
|
15
|
+
* robust to format drift and faithful to the "mirror the narrative for human
|
|
16
|
+
* traceability" goal. Both the top-level `## Wave History` form and the nested
|
|
17
|
+
* `### Wave History (…)` form (under `## Previous Session` in FeedFoundryV2) are handled.
|
|
18
|
+
*
|
|
19
|
+
* Exports:
|
|
20
|
+
* GENERATOR_MARKER — frontmatter sentinel identifying generator-owned files
|
|
21
|
+
* extractNarrative — pure: STATE.md contents → { waveHistory, deviations, whatNotToRetry, missionStatus }
|
|
22
|
+
* renderNarrative — pure: narrative + repo + now → full markdown (frontmatter + body)
|
|
23
|
+
* writeNarrative — idempotent write with skip-handwritten / skip-noop / dry-run + _overview refusal
|
|
24
|
+
* resolveNarrativePath — vaultDir + repoSlug → <vaultDir>/01-projects/<repoSlug>/_session-narrative.md
|
|
25
|
+
* mirrorNarrative — convenience: read STATE.md, resolve vault-dir from config, write (no-op when vault disabled)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
|
|
29
|
+
import { readFile } from 'node:fs/promises';
|
|
30
|
+
import path from 'node:path';
|
|
31
|
+
import os from 'node:os';
|
|
32
|
+
|
|
33
|
+
import { parseStateMd, parseMissionStatus } from '../state-md.mjs';
|
|
34
|
+
import {
|
|
35
|
+
parseFrontmatter,
|
|
36
|
+
toDate,
|
|
37
|
+
yamlQuoteIfNeeded,
|
|
38
|
+
subjectToSlug,
|
|
39
|
+
} from '../vault-mirror/utils.mjs';
|
|
40
|
+
import { readConfigFile, parseSessionConfig } from '../config.mjs';
|
|
41
|
+
import { validatePathInsideProject } from '../path-utils.mjs';
|
|
42
|
+
|
|
43
|
+
/** Frontmatter sentinel that identifies generator-owned narrative files. */
|
|
44
|
+
export const GENERATOR_MARKER = 'session-orchestrator-vault-status-narrative@1';
|
|
45
|
+
|
|
46
|
+
/** Placeholder used for noop comparison (replaces the live `updated:` value). */
|
|
47
|
+
const UPDATED_PLACEHOLDER = '__UPDATED_PLACEHOLDER__';
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The durable section headings we mirror, in render order. Each is extracted as
|
|
51
|
+
* a raw verbatim block (Discovery D3). Both the top-level `## <Name>` form and a
|
|
52
|
+
* nested `### <Name>` (optionally with a `(…)` suffix) are matched.
|
|
53
|
+
*/
|
|
54
|
+
const SECTION_TITLES = {
|
|
55
|
+
waveHistory: 'Wave History',
|
|
56
|
+
deviations: 'Deviations',
|
|
57
|
+
whatNotToRetry: 'What Not To Retry',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// ── Raw section extraction ──────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Expand a leading `~` to the current user's home directory.
|
|
64
|
+
*
|
|
65
|
+
* NOTE: deferred shared-helper extraction. The same `expandHome` pattern lives in
|
|
66
|
+
* other vault-status modules; W2 forbids introducing a shared new file, so this is
|
|
67
|
+
* inlined here. Consolidate into a shared util in a follow-up wave.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} p
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
function expandHome(p) {
|
|
73
|
+
if (typeof p !== 'string' || p.length === 0) return p;
|
|
74
|
+
if (p === '~') return os.homedir();
|
|
75
|
+
if (p.startsWith('~/')) return path.join(os.homedir(), p.slice(2));
|
|
76
|
+
return p;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Match an ATX markdown heading line. Returns `{ level, text }` or null.
|
|
81
|
+
*
|
|
82
|
+
* @param {string} line
|
|
83
|
+
* @returns {{ level: number, text: string } | null}
|
|
84
|
+
*/
|
|
85
|
+
function matchHeading(line) {
|
|
86
|
+
const m = /^(#{1,6})\s+(.*?)\s*$/.exec(line);
|
|
87
|
+
if (!m) return null;
|
|
88
|
+
return { level: m[1].length, text: m[2] };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Determine whether a heading text refers to the given section title, tolerating
|
|
93
|
+
* a trailing parenthetical (e.g. `Wave History (main-2026-06-18-1646, completed)`).
|
|
94
|
+
*
|
|
95
|
+
* @param {string} headingText
|
|
96
|
+
* @param {string} title
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
*/
|
|
99
|
+
function headingMatchesTitle(headingText, title) {
|
|
100
|
+
const stripped = headingText.replace(/\s*\(.*\)\s*$/, '').trim();
|
|
101
|
+
return stripped.toLowerCase() === title.toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract the RAW verbatim markdown block for a section by title.
|
|
106
|
+
*
|
|
107
|
+
* Finds the FIRST heading whose text matches `title` (top-level `##` or nested
|
|
108
|
+
* `###` form), then captures every line up to — but not including — the next
|
|
109
|
+
* heading of the SAME-OR-HIGHER level (i.e. heading.level <= openingLevel). The
|
|
110
|
+
* heading line itself is excluded; only the body content is returned. Trailing
|
|
111
|
+
* blank lines are trimmed; an empty/absent section yields ''.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} contents - full STATE.md body (or whole file; headings are scanned linewise)
|
|
114
|
+
* @param {string} title
|
|
115
|
+
* @returns {string}
|
|
116
|
+
*/
|
|
117
|
+
function extractSectionBlock(contents, title) {
|
|
118
|
+
if (typeof contents !== 'string' || contents.length === 0) return '';
|
|
119
|
+
const lines = contents.split('\n');
|
|
120
|
+
|
|
121
|
+
let openIdx = -1;
|
|
122
|
+
let openLevel = 0;
|
|
123
|
+
for (let i = 0; i < lines.length; i++) {
|
|
124
|
+
const h = matchHeading(lines[i]);
|
|
125
|
+
if (h && headingMatchesTitle(h.text, title)) {
|
|
126
|
+
openIdx = i;
|
|
127
|
+
openLevel = h.level;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (openIdx === -1) return '';
|
|
132
|
+
|
|
133
|
+
const collected = [];
|
|
134
|
+
for (let i = openIdx + 1; i < lines.length; i++) {
|
|
135
|
+
const h = matchHeading(lines[i]);
|
|
136
|
+
if (h && h.level <= openLevel) break; // next same-or-higher heading closes the block
|
|
137
|
+
collected.push(lines[i]);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Trim leading/trailing blank lines but preserve interior structure verbatim.
|
|
141
|
+
while (collected.length && collected[0].trim() === '') collected.shift();
|
|
142
|
+
while (collected.length && collected[collected.length - 1].trim() === '') collected.pop();
|
|
143
|
+
|
|
144
|
+
return collected.join('\n');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── Pure extraction ──────────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Extract the durable narrative from STATE.md contents.
|
|
151
|
+
*
|
|
152
|
+
* Returns raw verbatim section blocks plus the parsed mission-status rollup.
|
|
153
|
+
* Each section is the empty string when absent or empty. `missionStatus` is the
|
|
154
|
+
* frontmatter `mission-status:` array, or null when the key is absent (both real
|
|
155
|
+
* fixtures lack the frontmatter key — handled gracefully without crashing).
|
|
156
|
+
*
|
|
157
|
+
* Pure — no I/O. Never throws on malformed input (parseStateMd returns null).
|
|
158
|
+
*
|
|
159
|
+
* @param {string} stateMdContents
|
|
160
|
+
* @returns {{ waveHistory: string, deviations: string, whatNotToRetry: string, missionStatus: object[]|null }}
|
|
161
|
+
*/
|
|
162
|
+
export function extractNarrative(stateMdContents) {
|
|
163
|
+
const safe = typeof stateMdContents === 'string' ? stateMdContents : '';
|
|
164
|
+
|
|
165
|
+
const parsed = parseStateMd(safe);
|
|
166
|
+
const frontmatter = parsed && parsed.frontmatter ? parsed.frontmatter : null;
|
|
167
|
+
// Section extraction runs over the whole file: headings live in the body, and
|
|
168
|
+
// scanning the raw string is robust whether or not frontmatter parsed cleanly.
|
|
169
|
+
const body = parsed && typeof parsed.body === 'string' ? parsed.body : safe;
|
|
170
|
+
|
|
171
|
+
const missionStatus = frontmatter ? parseMissionStatus(frontmatter) : null;
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
waveHistory: extractSectionBlock(body, SECTION_TITLES.waveHistory),
|
|
175
|
+
deviations: extractSectionBlock(body, SECTION_TITLES.deviations),
|
|
176
|
+
whatNotToRetry: extractSectionBlock(body, SECTION_TITLES.whatNotToRetry),
|
|
177
|
+
missionStatus,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Pure render ────────────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Render a single mission-status rollup table from the parsed array.
|
|
185
|
+
*
|
|
186
|
+
* Tolerant of heterogeneous entry shapes (id/task/wave/status are read defensively).
|
|
187
|
+
*
|
|
188
|
+
* @param {object[]} missionStatus
|
|
189
|
+
* @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
function renderMissionTable(missionStatus) {
|
|
192
|
+
const lines = [];
|
|
193
|
+
lines.push('| ID | Task | Wave | Status |');
|
|
194
|
+
lines.push('|---|---|---|---|');
|
|
195
|
+
for (const entry of missionStatus) {
|
|
196
|
+
const e = entry && typeof entry === 'object' ? entry : {};
|
|
197
|
+
const id = e.id ?? '—';
|
|
198
|
+
// Escape pipes so a task description never breaks the table layout.
|
|
199
|
+
const task = String(e.task ?? '—').replace(/\|/g, '\\|');
|
|
200
|
+
const wave = e.wave ?? '—';
|
|
201
|
+
const status = e.status ?? '—';
|
|
202
|
+
lines.push(`| ${id} | ${task} | ${wave} | ${status} |`);
|
|
203
|
+
}
|
|
204
|
+
return lines.join('\n');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Render the full per-repo narrative markdown (frontmatter + body).
|
|
209
|
+
*
|
|
210
|
+
* Frontmatter ordering mirrors render-sessions.mjs conventions: id, type: session,
|
|
211
|
+
* title (double-quoted), created/updated (YYYY-MM-DD via toDate), repo, then
|
|
212
|
+
* `_generator` LAST. Body carries each verbatim section block (omitting empty
|
|
213
|
+
* sections from a "captured" claim but always emitting the heading so the file is
|
|
214
|
+
* structurally stable), plus a mission-status rollup table when present.
|
|
215
|
+
*
|
|
216
|
+
* Pure — no I/O.
|
|
217
|
+
*
|
|
218
|
+
* @param {{
|
|
219
|
+
* repo: string,
|
|
220
|
+
* narrative: { waveHistory: string, deviations: string, whatNotToRetry: string, missionStatus: object[]|null },
|
|
221
|
+
* now: Date,
|
|
222
|
+
* createdIso?: string,
|
|
223
|
+
* updatedPlaceholder?: string,
|
|
224
|
+
* }} opts
|
|
225
|
+
* @returns {string} full markdown
|
|
226
|
+
*/
|
|
227
|
+
export function renderNarrative(opts) {
|
|
228
|
+
const { repo, narrative, now, createdIso, updatedPlaceholder } = opts;
|
|
229
|
+
const n = narrative ?? { waveHistory: '', deviations: '', whatNotToRetry: '', missionStatus: null };
|
|
230
|
+
|
|
231
|
+
const nowIso = (now instanceof Date ? now : new Date()).toISOString();
|
|
232
|
+
const createdValue = toDate(createdIso ?? nowIso);
|
|
233
|
+
const updatedValue = updatedPlaceholder ?? toDate(nowIso);
|
|
234
|
+
|
|
235
|
+
const repoSlug = subjectToSlug(String(repo ?? 'unknown')) || 'unknown';
|
|
236
|
+
const noteId = `${repoSlug}-session-narrative`;
|
|
237
|
+
const titleValue = `${repo ?? 'unknown'} — Session Narrative`;
|
|
238
|
+
const title = yamlQuoteIfNeeded(titleValue) === titleValue ? `"${titleValue}"` : yamlQuoteIfNeeded(titleValue);
|
|
239
|
+
|
|
240
|
+
const lines = [];
|
|
241
|
+
|
|
242
|
+
// Frontmatter — `_generator` LAST (markdown-writer / render-sessions convention).
|
|
243
|
+
lines.push('---');
|
|
244
|
+
lines.push(`id: ${noteId}`);
|
|
245
|
+
lines.push('type: session');
|
|
246
|
+
lines.push(`title: ${title}`);
|
|
247
|
+
lines.push(`created: ${createdValue}`);
|
|
248
|
+
lines.push(`updated: ${updatedValue}`);
|
|
249
|
+
lines.push(`repo: ${yamlQuoteIfNeeded(String(repo ?? 'unknown'))}`);
|
|
250
|
+
lines.push(`_generator: ${GENERATOR_MARKER}`);
|
|
251
|
+
lines.push('---');
|
|
252
|
+
lines.push('');
|
|
253
|
+
|
|
254
|
+
// Title + preamble
|
|
255
|
+
lines.push(`# ${repo ?? 'unknown'} — Session Narrative`);
|
|
256
|
+
lines.push('');
|
|
257
|
+
lines.push(
|
|
258
|
+
'> Durable per-repo narrative mirrored from `.claude/STATE.md` (Epic #673). ' +
|
|
259
|
+
'What was done, what failed, and what not to retry — readable without opening the repo.',
|
|
260
|
+
);
|
|
261
|
+
lines.push('');
|
|
262
|
+
|
|
263
|
+
// Wave History
|
|
264
|
+
lines.push('## Wave History');
|
|
265
|
+
lines.push('');
|
|
266
|
+
lines.push(n.waveHistory && n.waveHistory.trim() ? n.waveHistory : '_(none recorded)_');
|
|
267
|
+
lines.push('');
|
|
268
|
+
|
|
269
|
+
// Deviations
|
|
270
|
+
lines.push('## Deviations');
|
|
271
|
+
lines.push('');
|
|
272
|
+
lines.push(n.deviations && n.deviations.trim() ? n.deviations : '_(none recorded)_');
|
|
273
|
+
lines.push('');
|
|
274
|
+
|
|
275
|
+
// What Not To Retry
|
|
276
|
+
lines.push('## What Not To Retry');
|
|
277
|
+
lines.push('');
|
|
278
|
+
lines.push(n.whatNotToRetry && n.whatNotToRetry.trim() ? n.whatNotToRetry : '_(none recorded)_');
|
|
279
|
+
lines.push('');
|
|
280
|
+
|
|
281
|
+
// Mission Status rollup — only when the frontmatter key was present + non-empty.
|
|
282
|
+
lines.push('## Mission Status');
|
|
283
|
+
lines.push('');
|
|
284
|
+
if (Array.isArray(n.missionStatus) && n.missionStatus.length > 0) {
|
|
285
|
+
lines.push(renderMissionTable(n.missionStatus));
|
|
286
|
+
} else {
|
|
287
|
+
lines.push('_(no mission-status rollup recorded)_');
|
|
288
|
+
}
|
|
289
|
+
lines.push('');
|
|
290
|
+
|
|
291
|
+
return lines.join('\n');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ── Path resolution ──────────────────────────────────────────────────────────────
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Resolve the generator-owned narrative file path inside the vault.
|
|
298
|
+
*
|
|
299
|
+
* @param {string} vaultDir
|
|
300
|
+
* @param {string} repoSlug
|
|
301
|
+
* @returns {string}
|
|
302
|
+
*/
|
|
303
|
+
export function resolveNarrativePath(vaultDir, repoSlug) {
|
|
304
|
+
return path.join(vaultDir, '01-projects', repoSlug, '_session-narrative.md');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ── Idempotent write ─────────────────────────────────────────────────────────────
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Normalize a markdown string by replacing the `updated:` frontmatter line with a
|
|
311
|
+
* stable placeholder, enabling byte-for-byte noop comparison.
|
|
312
|
+
*
|
|
313
|
+
* @param {string} content
|
|
314
|
+
* @returns {string}
|
|
315
|
+
*/
|
|
316
|
+
function normalizeUpdated(content) {
|
|
317
|
+
return content.replace(/^(updated:\s*)(.+)$/m, `$1${UPDATED_PLACEHOLDER}`);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Write the narrative file with idempotency guards (mirrors gitlab-portfolio
|
|
322
|
+
* markdown-writer.mjs contract).
|
|
323
|
+
*
|
|
324
|
+
* Guard order:
|
|
325
|
+
* 0. dry-run → never write, return { action: 'dry-run' }.
|
|
326
|
+
* 1. SAFETY (Epic #673 #1 risk): refuse if the target basename is `_overview.md`
|
|
327
|
+
* — that file is a hand-authored vault overview and must NEVER be clobbered by
|
|
328
|
+
* a generator. Return { action: 'skipped-handwritten' }.
|
|
329
|
+
* 2. skip-handwritten: existing file with no `_generator` or a FOREIGN `_generator`
|
|
330
|
+
* marker is hand-authored / owned by another generator → never overwrite.
|
|
331
|
+
* 3. skip-noop: rendered content (modulo the `updated:` timestamp) byte-identical
|
|
332
|
+
* to the existing content → no write.
|
|
333
|
+
* 4. otherwise write (mkdir -p the parent dir first).
|
|
334
|
+
*
|
|
335
|
+
* @param {{
|
|
336
|
+
* outputPath: string,
|
|
337
|
+
* content: string,
|
|
338
|
+
* dryRun?: boolean,
|
|
339
|
+
* fs?: { readFileSync?: Function, writeFileSync?: Function, mkdirSync?: Function, existsSync?: Function },
|
|
340
|
+
* }} opts
|
|
341
|
+
* @returns {{ action: 'written'|'skipped-handwritten'|'skipped-noop'|'dry-run', path: string }}
|
|
342
|
+
*/
|
|
343
|
+
export function writeNarrative(opts) {
|
|
344
|
+
const { outputPath, content, dryRun = false, fs: injectedFs } = opts;
|
|
345
|
+
|
|
346
|
+
const fsReadFile = injectedFs?.readFileSync ?? readFileSync;
|
|
347
|
+
const fsWriteFile = injectedFs?.writeFileSync ?? writeFileSync;
|
|
348
|
+
const fsMkdir = injectedFs?.mkdirSync ?? mkdirSync;
|
|
349
|
+
const fsExists = injectedFs?.existsSync ?? existsSync;
|
|
350
|
+
|
|
351
|
+
// Dry-run: never write.
|
|
352
|
+
if (dryRun) {
|
|
353
|
+
return { action: 'dry-run', path: outputPath };
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// SAFETY (Epic #673 #1 risk table): a generator must NEVER clobber the
|
|
357
|
+
// hand-authored vault `_overview.md`. Defense-in-depth alongside the
|
|
358
|
+
// dedicated `_session-narrative.md` target name and the marker guard below.
|
|
359
|
+
if (path.basename(outputPath) === '_overview.md') {
|
|
360
|
+
return { action: 'skipped-handwritten', path: outputPath };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (fsExists(outputPath)) {
|
|
364
|
+
let existingContent;
|
|
365
|
+
try {
|
|
366
|
+
existingContent = fsReadFile(outputPath, 'utf8');
|
|
367
|
+
} catch {
|
|
368
|
+
existingContent = null; // unreadable → treat as fresh write
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (existingContent !== null) {
|
|
372
|
+
const fm = parseFrontmatter(existingContent);
|
|
373
|
+
|
|
374
|
+
// skip-handwritten: no _generator or a foreign marker.
|
|
375
|
+
if (!fm || !fm['_generator']) {
|
|
376
|
+
return { action: 'skipped-handwritten', path: outputPath };
|
|
377
|
+
}
|
|
378
|
+
if (fm['_generator'] !== GENERATOR_MARKER) {
|
|
379
|
+
return { action: 'skipped-handwritten', path: outputPath };
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// skip-noop: content identical modulo the `updated:` timestamp.
|
|
383
|
+
if (normalizeUpdated(existingContent) === normalizeUpdated(content)) {
|
|
384
|
+
return { action: 'skipped-noop', path: outputPath };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
fsMkdir(path.dirname(outputPath), { recursive: true });
|
|
390
|
+
fsWriteFile(outputPath, content, 'utf8');
|
|
391
|
+
return { action: 'written', path: outputPath };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// ── Loose-slug folder matching (issue #829 Finding 3) ────────────────────────────
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Fold a slug candidate to a comparison-only form: lowercase, every
|
|
398
|
+
* non-alphanumeric character stripped. Used purely for equality comparison —
|
|
399
|
+
* never as a slug value itself. `subjectToSlug` (vault-mirror/utils.mjs) does
|
|
400
|
+
* NOT insert a hyphen at a camelCase boundary, so `GotzendorferV2` mints
|
|
401
|
+
* `gotzendorferv2` — a DIFFERENT folder than a hand-created `gotzendorfer-v2`.
|
|
402
|
+
* `looseSlug('gotzendorfer-v2') === looseSlug('gotzendorferv2')`, so the two
|
|
403
|
+
* forms can be reconciled without changing `subjectToSlug` itself.
|
|
404
|
+
*
|
|
405
|
+
* @param {string} s
|
|
406
|
+
* @returns {string}
|
|
407
|
+
*/
|
|
408
|
+
function looseSlug(s) {
|
|
409
|
+
return String(s ?? '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Loose-match a freshly-minted candidate slug against EXISTING `01-projects/`
|
|
414
|
+
* folder names before falling back to minting a new one (issue #829 Finding
|
|
415
|
+
* 3). When EXACTLY ONE existing folder's {@link looseSlug} equals the
|
|
416
|
+
* candidate's, that folder's EXACT on-disk name is reused (auto-resolving
|
|
417
|
+
* casing/punctuation drift, e.g. `GotzendorferV2` → `gotzendorfer-v2`,
|
|
418
|
+
* `LeadPipeDACH` → `leadpipe-dach`). Zero or MORE THAN ONE matches are
|
|
419
|
+
* ambiguous (or there is genuinely no match) — the caller's `subjectToSlug`
|
|
420
|
+
* candidate is returned unchanged, preserving current behaviour.
|
|
421
|
+
*
|
|
422
|
+
* Best-effort by design: any read failure (missing `01-projects/` dir,
|
|
423
|
+
* permission error, first-ever write for this vault, …) falls through to the
|
|
424
|
+
* candidate slug rather than throwing — a listing failure must never block a
|
|
425
|
+
* narrative write.
|
|
426
|
+
*
|
|
427
|
+
* @param {string} vaultDir
|
|
428
|
+
* @param {string} candidateSlug
|
|
429
|
+
* @param {{ readdirSync?: Function }} [fsSeam] — injectable `readdirSync` (test seam).
|
|
430
|
+
* @returns {string}
|
|
431
|
+
*/
|
|
432
|
+
function resolveLooseSlug(vaultDir, candidateSlug, fsSeam = {}) {
|
|
433
|
+
const readdir = fsSeam.readdirSync ?? readdirSync;
|
|
434
|
+
let entries;
|
|
435
|
+
try {
|
|
436
|
+
entries = readdir(path.join(vaultDir, '01-projects'), { withFileTypes: true });
|
|
437
|
+
} catch {
|
|
438
|
+
return candidateSlug;
|
|
439
|
+
}
|
|
440
|
+
if (!Array.isArray(entries)) return candidateSlug;
|
|
441
|
+
|
|
442
|
+
const candidateLoose = looseSlug(candidateSlug);
|
|
443
|
+
const matches = [];
|
|
444
|
+
for (const entry of entries) {
|
|
445
|
+
// Accept either a Dirent (real fs) or a plain string (a simplified test
|
|
446
|
+
// seam) — only Dirents that report as non-directories are excluded; a
|
|
447
|
+
// plain string is assumed to already denote a project folder.
|
|
448
|
+
const isDirent = entry && typeof entry === 'object' && typeof entry.name === 'string';
|
|
449
|
+
const name = isDirent ? entry.name : entry;
|
|
450
|
+
if (typeof name !== 'string' || name.length === 0) continue;
|
|
451
|
+
if (isDirent && typeof entry.isDirectory === 'function' && !entry.isDirectory()) continue;
|
|
452
|
+
if (looseSlug(name) === candidateLoose) matches.push(name);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
return matches.length === 1 ? matches[0] : candidateSlug;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ── Convenience orchestration ────────────────────────────────────────────────────
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Read a repo's STATE.md, resolve the vault-dir from Session Config, render the
|
|
462
|
+
* narrative, and write it idempotently.
|
|
463
|
+
*
|
|
464
|
+
* Silently no-ops (returns { action: 'skipped-vault-disabled' }) when
|
|
465
|
+
* `vault-integration.enabled` is false/absent or the vault-dir is unset. The
|
|
466
|
+
* resolved vault path is validated to live inside the (home-expanded) vault root
|
|
467
|
+
* before any write.
|
|
468
|
+
*
|
|
469
|
+
* @param {{
|
|
470
|
+
* repoRoot: string,
|
|
471
|
+
* repo: string,
|
|
472
|
+
* now?: Date,
|
|
473
|
+
* dryRun?: boolean,
|
|
474
|
+
* fs?: object,
|
|
475
|
+
* hostPaths?: { env?: Record<string, string|undefined>, ownerConfig?: object },
|
|
476
|
+
* }} opts
|
|
477
|
+
* `hostPaths` is forwarded verbatim to {@link parseSessionConfig}'s `hostPaths` DI
|
|
478
|
+
* seam (issue #653). Tests MUST pass a hermetic ctx (e.g. `{ env: {}, ownerConfig:
|
|
479
|
+
* undefined }`) when asserting a fixture's committed `vault-dir` — omitting it reads
|
|
480
|
+
* the REAL host `owner.yaml`, whose `paths.vault-dir` override (if set) wins over the
|
|
481
|
+
* fixture value and bleeds into the assertion (issue #783). Production callers omit
|
|
482
|
+
* this — the default (real owner.yaml resolution) is the correct host-local behavior.
|
|
483
|
+
* @returns {Promise<{ action: string, path?: string }>}
|
|
484
|
+
*/
|
|
485
|
+
export async function mirrorNarrative(opts) {
|
|
486
|
+
const { repoRoot, repo, now = new Date(), dryRun = false, fs: injectedFs, hostPaths } = opts;
|
|
487
|
+
|
|
488
|
+
if (typeof repoRoot !== 'string' || repoRoot.length === 0) {
|
|
489
|
+
return { action: 'skipped-vault-disabled' };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Defense-in-depth: when the caller omits (or passes an empty) `repo`, derive
|
|
493
|
+
// it from the repoRoot basename rather than mis-filing the narrative under the
|
|
494
|
+
// 'unknown' slug. A missing repo name must never silently mis-file (#675 review).
|
|
495
|
+
const repoName = (typeof repo === 'string' && repo.trim().length > 0)
|
|
496
|
+
? repo
|
|
497
|
+
: path.basename(path.resolve(repoRoot));
|
|
498
|
+
|
|
499
|
+
// Read Session Config (CLAUDE.md / AGENTS.md) and resolve vault settings.
|
|
500
|
+
let config;
|
|
501
|
+
try {
|
|
502
|
+
const configText = await readConfigFile(repoRoot);
|
|
503
|
+
config = parseSessionConfig(configText, { hostPaths });
|
|
504
|
+
} catch {
|
|
505
|
+
return { action: 'skipped-vault-disabled' };
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const vaultIntegration = config?.['vault-integration'];
|
|
509
|
+
if (!vaultIntegration || vaultIntegration.enabled !== true) {
|
|
510
|
+
return { action: 'skipped-vault-disabled' };
|
|
511
|
+
}
|
|
512
|
+
const rawVaultDir = vaultIntegration['vault-dir'];
|
|
513
|
+
if (!rawVaultDir || typeof rawVaultDir !== 'string') {
|
|
514
|
+
return { action: 'skipped-vault-disabled' };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const vaultDir = path.resolve(expandHome(rawVaultDir));
|
|
518
|
+
const candidateSlug = subjectToSlug(repoName) || 'unknown';
|
|
519
|
+
// Loose-match against existing 01-projects/ folders before minting a new
|
|
520
|
+
// slug (issue #829 Finding 3) — see resolveLooseSlug for the ambiguity
|
|
521
|
+
// rules. Falls through to `candidateSlug` unchanged on any read failure.
|
|
522
|
+
const repoSlug = resolveLooseSlug(vaultDir, candidateSlug, { readdirSync: injectedFs?.readdirSync });
|
|
523
|
+
const outputPath = resolveNarrativePath(vaultDir, repoSlug);
|
|
524
|
+
|
|
525
|
+
// Defense-in-depth: ensure the resolved file stays inside the vault root.
|
|
526
|
+
const inside = validatePathInsideProject(path.relative(vaultDir, outputPath), vaultDir);
|
|
527
|
+
if (!inside.ok) {
|
|
528
|
+
return { action: 'skipped-invalid-path', path: outputPath };
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Read STATE.md (best-effort; absent STATE.md → nothing to mirror).
|
|
532
|
+
const stateMdPath = path.join(repoRoot, '.claude', 'STATE.md');
|
|
533
|
+
let stateContents;
|
|
534
|
+
try {
|
|
535
|
+
stateContents = await readFile(stateMdPath, 'utf8');
|
|
536
|
+
} catch {
|
|
537
|
+
return { action: 'skipped-no-statemd', path: outputPath };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const narrative = extractNarrative(stateContents);
|
|
541
|
+
const content = renderNarrative({ repo: repoName, narrative, now });
|
|
542
|
+
|
|
543
|
+
return writeNarrative({ outputPath, content, dryRun, fs: injectedFs });
|
|
544
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vault-sync-baseline.mjs — Snapshot writer + diff comparator for vault-sync #327.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions (no top-level side effects). The only function that writes to
|
|
5
|
+
* disk is `writeBaseline()`, which uses an atomic write-temp-then-rename pattern
|
|
6
|
+
* identical to the one used in scripts/lib/learnings.mjs.
|
|
7
|
+
*
|
|
8
|
+
* Exports:
|
|
9
|
+
* computeSchemaHash(schemaText) → string (8-char SHA-256 hex prefix)
|
|
10
|
+
* writeBaseline(filePath, payload) → void (atomic fs write)
|
|
11
|
+
* readBaseline(filePath) → object|null
|
|
12
|
+
* diffBaseline({ baselineErrors, currentErrors }) → DiffResult
|
|
13
|
+
*
|
|
14
|
+
* No external dependencies — Node 20+ stdlib (node:crypto, node:fs, node:path) only.
|
|
15
|
+
*
|
|
16
|
+
* Issue: #327 vault-sync baseline-diff reporting.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { digestSha256Short } from './crypto-digest-utils.mjs';
|
|
20
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync, existsSync } from 'node:fs';
|
|
21
|
+
import { dirname } from 'node:path';
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// computeSchemaHash
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Compute a stable 8-character SHA-256 hex prefix of the given schema text.
|
|
29
|
+
*
|
|
30
|
+
* The caller is responsible for extracting the relevant text (e.g. the block
|
|
31
|
+
* between BEGIN/END GENERATED SCHEMA sentinels in validator.mjs).
|
|
32
|
+
*
|
|
33
|
+
* @param {string} schemaText
|
|
34
|
+
* @returns {string} 8-character lowercase hex prefix of SHA-256 digest
|
|
35
|
+
*/
|
|
36
|
+
export function computeSchemaHash(schemaText) {
|
|
37
|
+
return digestSha256Short(schemaText);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// writeBaseline
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Serialize a baseline snapshot to disk using an atomic write-temp-then-rename
|
|
46
|
+
* pattern. Creates intermediate directories if needed.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} filePath Absolute path to the target baseline JSON file.
|
|
49
|
+
* @param {{ errors: object[], warnings: object[], schemaHash: string, isoTimestamp: string, vaultDir: string }} payload
|
|
50
|
+
* @returns {void}
|
|
51
|
+
*/
|
|
52
|
+
export function writeBaseline(filePath, { errors, warnings, schemaHash, isoTimestamp, vaultDir }) {
|
|
53
|
+
const doc = {
|
|
54
|
+
schema_hash: schemaHash,
|
|
55
|
+
generated_at: isoTimestamp,
|
|
56
|
+
vault_dir: vaultDir,
|
|
57
|
+
error_count: errors.length,
|
|
58
|
+
warning_count: warnings.length,
|
|
59
|
+
errors,
|
|
60
|
+
warnings,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const dir = dirname(filePath);
|
|
64
|
+
mkdirSync(dir, { recursive: true });
|
|
65
|
+
|
|
66
|
+
const tmp = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
67
|
+
writeFileSync(tmp, JSON.stringify(doc, null, 2) + '\n', 'utf8');
|
|
68
|
+
renameSync(tmp, filePath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// readBaseline
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Read and parse a baseline snapshot file.
|
|
77
|
+
*
|
|
78
|
+
* Returns null (never throws) if the file is missing, unreadable, or
|
|
79
|
+
* contains malformed JSON or an unexpected shape.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} filePath Absolute path to the baseline JSON file.
|
|
82
|
+
* @returns {{ schemaHash: string, errors: object[], warnings: object[], generated_at: string, vault_dir: string, error_count: number, warning_count: number } | null}
|
|
83
|
+
*/
|
|
84
|
+
export function readBaseline(filePath) {
|
|
85
|
+
if (!existsSync(filePath)) return null;
|
|
86
|
+
try {
|
|
87
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
88
|
+
const doc = JSON.parse(raw);
|
|
89
|
+
if (
|
|
90
|
+
typeof doc !== 'object' ||
|
|
91
|
+
doc === null ||
|
|
92
|
+
typeof doc.schema_hash !== 'string' ||
|
|
93
|
+
!Array.isArray(doc.errors) ||
|
|
94
|
+
!Array.isArray(doc.warnings)
|
|
95
|
+
) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
schemaHash: doc.schema_hash,
|
|
100
|
+
generated_at: doc.generated_at ?? '',
|
|
101
|
+
vault_dir: doc.vault_dir ?? '',
|
|
102
|
+
error_count: doc.error_count ?? doc.errors.length,
|
|
103
|
+
warning_count: doc.warning_count ?? doc.warnings.length,
|
|
104
|
+
errors: doc.errors,
|
|
105
|
+
warnings: doc.warnings,
|
|
106
|
+
};
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// diffBaseline
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Compute a set-difference between a baseline error (or warning) array and the
|
|
118
|
+
* current run's array. Identity is determined by the triple (file, path, message).
|
|
119
|
+
*
|
|
120
|
+
* Call once for errors, once for warnings — the function is symmetric.
|
|
121
|
+
*
|
|
122
|
+
* @param {{ baselineErrors: object[], currentErrors: object[] }} opts
|
|
123
|
+
* @returns {{ newErrors: object[], resolvedErrors: object[], baselineCount: number, currentCount: number }}
|
|
124
|
+
*/
|
|
125
|
+
export function diffBaseline({ baselineErrors, currentErrors }) {
|
|
126
|
+
/**
|
|
127
|
+
* Build a stable string key from an error/warning entry.
|
|
128
|
+
* Falls back gracefully when fields are missing.
|
|
129
|
+
*
|
|
130
|
+
* @param {object} e
|
|
131
|
+
* @returns {string}
|
|
132
|
+
*/
|
|
133
|
+
function entryKey(e) {
|
|
134
|
+
const file = e.file ?? '';
|
|
135
|
+
const path = e.path ?? (e.type ?? '');
|
|
136
|
+
const message = e.message ?? '';
|
|
137
|
+
return `${file}\x00${path}\x00${message}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const baselineSet = new Set((baselineErrors ?? []).map(entryKey));
|
|
141
|
+
const currentSet = new Set((currentErrors ?? []).map(entryKey));
|
|
142
|
+
|
|
143
|
+
const newErrors = (currentErrors ?? []).filter((e) => !baselineSet.has(entryKey(e)));
|
|
144
|
+
const resolvedErrors = (baselineErrors ?? []).filter((e) => !currentSet.has(entryKey(e)));
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
newErrors,
|
|
148
|
+
resolvedErrors,
|
|
149
|
+
baselineCount: (baselineErrors ?? []).length,
|
|
150
|
+
currentCount: (currentErrors ?? []).length,
|
|
151
|
+
};
|
|
152
|
+
}
|