kenaz 0.0.0-stage → 0.3.2
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/bin/kenaz.js +3 -0
- package/core/.agents/skills/remotion-best-practices/SKILL.md +61 -0
- package/core/.agents/skills/remotion-best-practices/rules/3d.md +86 -0
- package/core/.agents/skills/remotion-best-practices/rules/animations.md +27 -0
- package/core/.agents/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/core/.agents/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/core/.agents/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
- package/core/.agents/skills/remotion-best-practices/rules/assets.md +78 -0
- package/core/.agents/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
- package/core/.agents/skills/remotion-best-practices/rules/audio.md +169 -0
- package/core/.agents/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
- package/core/.agents/skills/remotion-best-practices/rules/can-decode.md +81 -0
- package/core/.agents/skills/remotion-best-practices/rules/charts.md +120 -0
- package/core/.agents/skills/remotion-best-practices/rules/compositions.md +154 -0
- package/core/.agents/skills/remotion-best-practices/rules/display-captions.md +184 -0
- package/core/.agents/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/core/.agents/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
- package/core/.agents/skills/remotion-best-practices/rules/fonts.md +152 -0
- package/core/.agents/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/core/.agents/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/core/.agents/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
- package/core/.agents/skills/remotion-best-practices/rules/gifs.md +141 -0
- package/core/.agents/skills/remotion-best-practices/rules/images.md +134 -0
- package/core/.agents/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
- package/core/.agents/skills/remotion-best-practices/rules/light-leaks.md +73 -0
- package/core/.agents/skills/remotion-best-practices/rules/lottie.md +70 -0
- package/core/.agents/skills/remotion-best-practices/rules/maps.md +412 -0
- package/core/.agents/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
- package/core/.agents/skills/remotion-best-practices/rules/measuring-text.md +140 -0
- package/core/.agents/skills/remotion-best-practices/rules/parameters.md +109 -0
- package/core/.agents/skills/remotion-best-practices/rules/sequencing.md +118 -0
- package/core/.agents/skills/remotion-best-practices/rules/sfx.md +30 -0
- package/core/.agents/skills/remotion-best-practices/rules/subtitles.md +36 -0
- package/core/.agents/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/core/.agents/skills/remotion-best-practices/rules/text-animations.md +20 -0
- package/core/.agents/skills/remotion-best-practices/rules/timing.md +179 -0
- package/core/.agents/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
- package/core/.agents/skills/remotion-best-practices/rules/transitions.md +197 -0
- package/core/.agents/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
- package/core/.agents/skills/remotion-best-practices/rules/trimming.md +51 -0
- package/core/.agents/skills/remotion-best-practices/rules/videos.md +171 -0
- package/core/.agents/skills/remotion-best-practices/rules/voiceover.md +99 -0
- package/core/.agents/skills/supabase-postgres-best-practices/AGENTS.md +90 -0
- package/core/.agents/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
- package/core/.agents/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/query-index-types.md +45 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/core/.agents/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
- package/core/.agents/skills/typesafe-ai/LICENSE +21 -0
- package/core/.agents/skills/typesafe-ai/SKILL.md +149 -0
- package/core/.agents/skills/vercel-react-best-practices/AGENTS.md +2934 -0
- package/core/.agents/skills/vercel-react-best-practices/SKILL.md +136 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/core/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/core/.claude/agents/pcb-reviewer.md +146 -0
- package/core/.claude/commands/Amelia.md +5 -0
- package/core/.claude/commands/Ansuz.md +38 -0
- package/core/.claude/commands/Huginn.md +25 -0
- package/core/.claude/commands/agent-worker.md +168 -0
- package/core/.claude/commands/architect.md +24 -0
- package/core/.claude/commands/archive.md +24 -0
- package/core/.claude/commands/canary.md +17 -0
- package/core/.claude/commands/incident.md +15 -0
- package/core/.claude/commands/init.md +112 -0
- package/core/.claude/commands/legacy_dev.md +18 -0
- package/core/.claude/commands/product-review.md +131 -0
- package/core/.claude/commands/review.md +14 -0
- package/core/.claude/commands/security.md +15 -0
- package/core/.claude/commands/spec.md +22 -0
- package/core/.claude/commands/strategy.md +141 -0
- package/core/.claude/commands/sunny.md +1 -0
- package/core/.claude/commands//347/224/242/345/223/201.md +15 -0
- package/core/.claude/commands//347/224/242/345/223/201/346/252/242/350/246/226.md +129 -0
- package/core/.claude/commands//350/250/255/350/250/210.md +14 -0
- package/core/.claude/commands//350/251/225/344/274/260.md +16 -0
- package/core/.claude/commands//351/200/262/345/272/246.md +15 -0
- package/core/.claude/hooks/__tests__/substrate.test.js +122 -0
- package/core/.claude/hooks/__tests__/task-1866-cli-guard.test.js +193 -0
- package/core/.claude/hooks/__tests__/task-1977-dedupe-guard.test.js +125 -0
- package/core/.claude/hooks/__tests__/task-2087-mark-landed-idempotent.test.js +128 -0
- package/core/.claude/hooks/__tests__/task-2253-null-tip-marker-reuse.test.js +84 -0
- package/core/.claude/hooks/__tests__/task-2381-project-guard-walkup.test.js +53 -0
- package/core/.claude/hooks/_ansuz-conversation.js +183 -0
- package/core/.claude/hooks/_ansuz-conversation.test.js +90 -0
- package/core/.claude/hooks/_dedupe-guard.js +75 -0
- package/core/.claude/hooks/_peer-project-root.js +113 -0
- package/core/.claude/hooks/_peer-project-root.test.js +121 -0
- package/core/.claude/hooks/_peer-waiting-line.js +27 -0
- package/core/.claude/hooks/_project-guard.js +45 -0
- package/core/.claude/hooks/_run-user-prompt.js +134 -0
- package/core/.claude/hooks/_run-user-prompt.test.js +80 -0
- package/core/.claude/hooks/_session-identity.js +62 -0
- package/core/.claude/hooks/_session-pin.js +34 -0
- package/core/.claude/hooks/_session-pin.test.js +89 -0
- package/core/.claude/hooks/_signal-fence.js +17 -0
- package/core/.claude/hooks/_substrate-paths.js +95 -0
- package/core/.claude/hooks/_user-scope.js +23 -0
- package/core/.claude/hooks/ansuz-session-pin-via-turn-context.test.js +52 -0
- package/core/.claude/hooks/ansuz-session-pin.js +171 -0
- package/core/.claude/hooks/ansuz-session-pin.test.js +140 -0
- package/core/.claude/hooks/context-check.js +51 -0
- package/core/.claude/hooks/core-resolve.js +230 -0
- package/core/.claude/hooks/daemon-outbox-hook.js +139 -0
- package/core/.claude/hooks/fatigue-check.js +52 -0
- package/core/.claude/hooks/handoff-snapshot-on-commit.js +154 -0
- package/core/.claude/hooks/handoff-snapshot-on-decision.js +188 -0
- package/core/.claude/hooks/handoff-snapshot-on-question.js +158 -0
- package/core/.claude/hooks/handoff-staleness.js +197 -0
- package/core/.claude/hooks/jev-thalamus-context.js +17 -0
- package/core/.claude/hooks/jev-thalamus-context.test.js +41 -0
- package/core/.claude/hooks/lease-release.js +119 -0
- package/core/.claude/hooks/lease-touch.js +96 -0
- package/core/.claude/hooks/memory-guard.js +723 -0
- package/core/.claude/hooks/memory-guard.test.js +574 -0
- package/core/.claude/hooks/reflex-feedback.js +5 -0
- package/core/.claude/hooks/session-init.ps1 +49 -0
- package/core/.claude/hooks/sound-event.js +70 -0
- package/core/.claude/hooks/sound-event.sh +37 -0
- package/core/.claude/hooks/stream-delta.js +169 -0
- package/core/.claude/hooks/synapse-first-investigation.js +326 -0
- package/core/.claude/hooks/synapse-first-investigation.test.js +126 -0
- package/core/.claude/hooks/task-bridge.js +33 -0
- package/core/.claude/hooks/turn-context-peer.test.js +115 -0
- package/core/.claude/hooks/turn-context-session-line.test.js +69 -0
- package/core/.claude/hooks/turn-context.js +215 -0
- package/core/.claude/hooks/worktree-create.js +1232 -0
- package/core/.claude/hooks/worktree-remove.js +181 -0
- package/core/.claude/no-hooks.settings.json +3 -0
- package/core/.claude/settings.json +180 -0
- package/core/.claude/skills/adapt/SKILL.md +199 -0
- package/core/.claude/skills/animate/SKILL.md +190 -0
- package/core/.claude/skills/architect_agent/SKILL.md +408 -0
- package/core/.claude/skills/assessment_agent/SKILL.md +289 -0
- package/core/.claude/skills/audit/SKILL.md +129 -0
- package/core/.claude/skills/backend_agent/SKILL.md +407 -0
- package/core/.claude/skills/backend_engineer/SKILL.md +788 -0
- package/core/.claude/skills/bolder/SKILL.md +132 -0
- package/core/.claude/skills/clarify/SKILL.md +180 -0
- package/core/.claude/skills/code_reviewer/SKILL.md +305 -0
- package/core/.claude/skills/code_reviewer/patterns.yaml +85 -0
- package/core/.claude/skills/colorize/SKILL.md +158 -0
- package/core/.claude/skills/critique/SKILL.md +118 -0
- package/core/.claude/skills/delight/SKILL.md +317 -0
- package/core/.claude/skills/designer/SKILL.md +845 -0
- package/core/.claude/skills/dev_agent/SKILL.md +503 -0
- package/core/.claude/skills/developer/SKILL.md +792 -0
- package/core/.claude/skills/distill/SKILL.md +137 -0
- package/core/.claude/skills/extract/SKILL.md +95 -0
- package/core/.claude/skills/feature_agent/SKILL.md +303 -0
- package/core/.claude/skills/frontend-design/SKILL.md +127 -0
- package/core/.claude/skills/frontend-design/reference/color-and-contrast.md +132 -0
- package/core/.claude/skills/frontend-design/reference/interaction-design.md +123 -0
- package/core/.claude/skills/frontend-design/reference/motion-design.md +99 -0
- package/core/.claude/skills/frontend-design/reference/responsive-design.md +114 -0
- package/core/.claude/skills/frontend-design/reference/spatial-design.md +100 -0
- package/core/.claude/skills/frontend-design/reference/typography.md +131 -0
- package/core/.claude/skills/frontend-design/reference/ux-writing.md +107 -0
- package/core/.claude/skills/frontend_agent/SKILL.md +327 -0
- package/core/.claude/skills/harden/SKILL.md +358 -0
- package/core/.claude/skills/heimdall/SKILL.md +84 -0
- package/core/.claude/skills/huginn/SKILL.md +208 -0
- package/core/.claude/skills/incident_response/SKILL.md +315 -0
- package/core/.claude/skills/legacy_dev/SKILL.md +202 -0
- package/core/.claude/skills/normalize/SKILL.md +67 -0
- package/core/.claude/skills/onboard/SKILL.md +243 -0
- package/core/.claude/skills/optimization_agent/SKILL.md +340 -0
- package/core/.claude/skills/optimize/SKILL.md +269 -0
- package/core/.claude/skills/pcb-review/SKILL.md +39 -0
- package/core/.claude/skills/polish/SKILL.md +202 -0
- package/core/.claude/skills/product_manager/SKILL.md +1091 -0
- package/core/.claude/skills/qa_engineer/SKILL.md +1269 -0
- package/core/.claude/skills/quieter/SKILL.md +118 -0
- package/core/.claude/skills/remotion-best-practices/SKILL.md +61 -0
- package/core/.claude/skills/remotion-best-practices/rules/3d.md +86 -0
- package/core/.claude/skills/remotion-best-practices/rules/animations.md +27 -0
- package/core/.claude/skills/remotion-best-practices/rules/assets/charts-bar-chart.tsx +173 -0
- package/core/.claude/skills/remotion-best-practices/rules/assets/text-animations-typewriter.tsx +100 -0
- package/core/.claude/skills/remotion-best-practices/rules/assets/text-animations-word-highlight.tsx +103 -0
- package/core/.claude/skills/remotion-best-practices/rules/assets.md +78 -0
- package/core/.claude/skills/remotion-best-practices/rules/audio-visualization.md +198 -0
- package/core/.claude/skills/remotion-best-practices/rules/audio.md +169 -0
- package/core/.claude/skills/remotion-best-practices/rules/calculate-metadata.md +134 -0
- package/core/.claude/skills/remotion-best-practices/rules/can-decode.md +81 -0
- package/core/.claude/skills/remotion-best-practices/rules/charts.md +120 -0
- package/core/.claude/skills/remotion-best-practices/rules/compositions.md +154 -0
- package/core/.claude/skills/remotion-best-practices/rules/display-captions.md +184 -0
- package/core/.claude/skills/remotion-best-practices/rules/extract-frames.md +229 -0
- package/core/.claude/skills/remotion-best-practices/rules/ffmpeg.md +38 -0
- package/core/.claude/skills/remotion-best-practices/rules/fonts.md +152 -0
- package/core/.claude/skills/remotion-best-practices/rules/get-audio-duration.md +58 -0
- package/core/.claude/skills/remotion-best-practices/rules/get-video-dimensions.md +68 -0
- package/core/.claude/skills/remotion-best-practices/rules/get-video-duration.md +60 -0
- package/core/.claude/skills/remotion-best-practices/rules/gifs.md +141 -0
- package/core/.claude/skills/remotion-best-practices/rules/images.md +134 -0
- package/core/.claude/skills/remotion-best-practices/rules/import-srt-captions.md +69 -0
- package/core/.claude/skills/remotion-best-practices/rules/light-leaks.md +73 -0
- package/core/.claude/skills/remotion-best-practices/rules/lottie.md +70 -0
- package/core/.claude/skills/remotion-best-practices/rules/maps.md +412 -0
- package/core/.claude/skills/remotion-best-practices/rules/measuring-dom-nodes.md +34 -0
- package/core/.claude/skills/remotion-best-practices/rules/measuring-text.md +140 -0
- package/core/.claude/skills/remotion-best-practices/rules/parameters.md +109 -0
- package/core/.claude/skills/remotion-best-practices/rules/sequencing.md +118 -0
- package/core/.claude/skills/remotion-best-practices/rules/sfx.md +30 -0
- package/core/.claude/skills/remotion-best-practices/rules/subtitles.md +36 -0
- package/core/.claude/skills/remotion-best-practices/rules/tailwind.md +11 -0
- package/core/.claude/skills/remotion-best-practices/rules/text-animations.md +20 -0
- package/core/.claude/skills/remotion-best-practices/rules/timing.md +179 -0
- package/core/.claude/skills/remotion-best-practices/rules/transcribe-captions.md +70 -0
- package/core/.claude/skills/remotion-best-practices/rules/transitions.md +197 -0
- package/core/.claude/skills/remotion-best-practices/rules/transparent-videos.md +106 -0
- package/core/.claude/skills/remotion-best-practices/rules/trimming.md +51 -0
- package/core/.claude/skills/remotion-best-practices/rules/videos.md +171 -0
- package/core/.claude/skills/remotion-best-practices/rules/voiceover.md +99 -0
- package/core/.claude/skills/security_agent/SKILL.md +312 -0
- package/core/.claude/skills/spec_optimizer/SKILL.md +299 -0
- package/core/.claude/skills/sre_agent/SKILL.md +1347 -0
- package/core/.claude/skills/sunny/SKILL.md +74 -0
- package/core/.claude/skills/supabase-postgres-best-practices/AGENTS.md +90 -0
- package/core/.claude/skills/supabase-postgres-best-practices/CLAUDE.md +1 -0
- package/core/.claude/skills/supabase-postgres-best-practices/SKILL.md +64 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/query-index-types.md +45 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
- package/core/.claude/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
- package/core/.claude/skills/synapse_maintain/SKILL.md +84 -0
- package/core/.claude/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/core/.claude/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/core/.claude/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/core/.claude/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/core/.claude/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/core/.claude/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/core/.claude/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/core/.claude/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/core/.claude/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/core/.claude/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/core/.claude/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/core/.claude/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/core/.claude/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/core/.claude/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/core/.claude/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/core/.claude/skills/ui-ux-pro-max/scripts/search.py +106 -0
- package/core/.claude/skills/vercel-react-best-practices/AGENTS.md +2934 -0
- package/core/.claude/skills/vercel-react-best-practices/SKILL.md +136 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/core/.claude/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
- package/core/.collaboration/README.md +258 -0
- package/core/.collaboration/cli/commands/assign.js +39 -0
- package/core/.collaboration/cli/commands/claim.js +33 -0
- package/core/.collaboration/cli/commands/complete.js +44 -0
- package/core/.collaboration/cli/commands/create.js +110 -0
- package/core/.collaboration/cli/commands/get.js +83 -0
- package/core/.collaboration/cli/commands/index.js +52 -0
- package/core/.collaboration/cli/commands/list.js +51 -0
- package/core/.collaboration/cli/commands/lock.js +45 -0
- package/core/.collaboration/cli/commands/rebuild.js +39 -0
- package/core/.collaboration/cli/commands/search.js +61 -0
- package/core/.collaboration/cli/commands/stats.js +61 -0
- package/core/.collaboration/cli/commands/transfer.js +41 -0
- package/core/.collaboration/cli/commands/unlock.js +48 -0
- package/core/.collaboration/cli/formatters/help-formatter.js +70 -0
- package/core/.collaboration/cli/formatters/index.js +7 -0
- package/core/.collaboration/cli/task-cli.js +46 -0
- package/core/.collaboration/cli/utils/arg-parser.js +74 -0
- package/core/.collaboration/cli/utils/git-user.js +25 -0
- package/core/.collaboration/cli/utils/index.js +9 -0
- package/core/.collaboration/cli/utils/output.js +10 -0
- package/core/.collaboration/config/default.yaml +189 -0
- package/core/.collaboration/core/conflict-resolver.js +461 -0
- package/core/.collaboration/core/conflict-resolver.test.js +389 -0
- package/core/.collaboration/core/id-generator.js +303 -0
- package/core/.collaboration/core/migrate-legacy.js +260 -0
- package/core/.collaboration/core/package.json +1 -0
- package/core/.collaboration/core/task-2257-estimated-hours.test.js +72 -0
- package/core/.collaboration/core/task-2260-dir-layout-move.test.js +60 -0
- package/core/.collaboration/core/task-2264-single-after-capture.test.js +61 -0
- package/core/.collaboration/core/task-2288-followups.test.js +121 -0
- package/core/.collaboration/core/task-api-json.js +211 -0
- package/core/.collaboration/core/task-api.js +3729 -0
- package/core/.collaboration/core/task-api.test.js +1268 -0
- package/core/.collaboration/docs/API.md +787 -0
- package/core/.collaboration/docs/ARCHITECTURE.md +596 -0
- package/core/.collaboration/docs/CLI.md +821 -0
- package/core/.collaboration/docs/FAQ.md +695 -0
- package/core/.collaboration/docs/MIGRATION.md +804 -0
- package/core/.collaboration/docs/examples/README.md +320 -0
- package/core/.collaboration/docs/examples/advanced-queries.js +261 -0
- package/core/.collaboration/docs/examples/basic-usage.js +184 -0
- package/core/.collaboration/docs/examples/team-workflow.js +352 -0
- package/core/.collaboration/hooks/README.md +70 -0
- package/core/.collaboration/hooks/config.json +10 -0
- package/core/.collaboration/hooks/install.js +400 -0
- package/core/.collaboration/hooks/post-merge.js +163 -0
- package/core/.collaboration/hooks/pre-commit.js +264 -0
- package/core/.collaboration/hooks/synapse-hook.js +486 -0
- package/core/.collaboration/hooks/synapse-hook.test.js +69 -0
- package/core/.failure/INDEX.yaml +231 -0
- package/core/.failure/agent-execution/FAIL-001-agent-no-output.yaml +391 -0
- package/core/.failure/agent-execution/FAIL-004_agent_platform_specific_commands.md +194 -0
- package/core/.failure/agent-execution/FAIL-005_background_agent_permission_denied.md +204 -0
- package/core/.failure/agent-execution/FAIL-007-pm-lead-wrong-team-invocation.yaml +0 -0
- package/core/.failure/agent-execution/FAIL-008-bypass-api-direct-yaml-write.yaml +94 -0
- package/core/.failure/agent-execution/FAIL-010-phase0-poem-format-skipped.yaml +28 -0
- package/core/.failure/agent-execution/FAIL-010-skipping-cognitive-checkpoint-after-verification.yaml +59 -0
- package/core/.failure/agent-execution/FAIL-012-grep-shortcut-during-investigation.yaml +85 -0
- package/core/.failure/agent-execution/FAIL-013-search-cwd-drift-and-case-mismatch.yaml +103 -0
- package/core/.failure/agent-execution/FAIL-014-shared-worktree-premature-death-verdict.yaml +35 -0
- package/core/.failure/agent-execution/FAIL-AUTO-TASK_1413.yaml +31 -0
- package/core/.failure/agent-execution/FAIL-AUTO-TASK_1427.yaml +41 -0
- package/core/.failure/agent-execution/FAIL-AUTO-TASK_904.yaml +30 -0
- package/core/.failure/agent-execution/README.md +68 -0
- package/core/.failure/architecture/FAIL-004_session_based_skill_recognition.md +128 -0
- package/core/.failure/architecture/FAIL-006-python-version-compatibility.yaml +445 -0
- package/core/.failure/architecture/README.md +87 -0
- package/core/.failure/code-quality/README.md +89 -0
- package/core/.failure/data-integrity/FAIL-002-json-bom.yaml +220 -0
- package/core/.failure/data-integrity/README.md +141 -0
- package/core/.failure/pipeline/FAIL-003-queue-pollution.yaml +335 -0
- package/core/.failure/pipeline/README.md +95 -0
- package/core/.failure/retired/FAIL-009-skipping-synapse-pulse-before-code-change.yaml +49 -0
- package/core/.failure/tools/README.md +230 -0
- package/core/.failure/tools/USAGE_GUIDE.md +323 -0
- package/core/.failure/tools/check_bom.js +101 -0
- package/core/.failure/tools/check_failures.ps1 +387 -0
- package/core/.failure/tools/fix_json.js +58 -0
- package/core/.failure/tools/log_failure.ps1 +474 -0
- package/core/.failure/tools/remove_bom.js +46 -0
- package/core/.failure/tools/validate_json.js +50 -0
- package/core/.formation/.episode-input.json +10 -0
- package/core/.formation/README.md +188 -0
- package/core/.formation/bifrost/CGDD_hook_lifecycle.js +575 -0
- package/core/.formation/bifrost/HOOK_PARITY_VERIFICATION.md +241 -0
- package/core/.formation/bifrost/README.md +71 -0
- package/core/.formation/bifrost/codex-doctor.js +106 -0
- package/core/.formation/bifrost/codex-doctor.test.js +51 -0
- package/core/.formation/bifrost/launch.js +510 -0
- package/core/.formation/bifrost/launch.test.js +217 -0
- package/core/.formation/bifrost/openai/codex-hook-shim.js +171 -0
- package/core/.formation/bifrost/openai/codex-hook-shim.test.js +107 -0
- package/core/.formation/bifrost/openai/models.json +36 -0
- package/core/.formation/bifrost/openai/relay.js +781 -0
- package/core/.formation/bifrost/openai/relay.test.js +71 -0
- package/core/.formation/bifrost/session.js +382 -0
- package/core/.formation/bifrost/session.test.js +121 -0
- package/core/.formation/bifrost/setup-codex.js +685 -0
- package/core/.formation/bifrost/setup-codex.test.js +102 -0
- package/core/.formation/bifrost/shared/ansuz-boot-policy.js +31 -0
- package/core/.formation/bifrost/shared/ansuz-boot-policy.test.js +40 -0
- package/core/.formation/bifrost/shared/boot-context.js +68 -0
- package/core/.formation/bifrost/shared/boot-context.test.js +67 -0
- package/core/.formation/bifrost/shared/codex-appserver.js +191 -0
- package/core/.formation/bifrost/shared/codex-injection.js +126 -0
- package/core/.formation/bifrost/shared/codex-session-registry.js +105 -0
- package/core/.formation/bifrost/shared/codex-session-registry.test.js +126 -0
- package/core/.formation/bifrost/shared/consolidate.js +286 -0
- package/core/.formation/bifrost/shared/stream-json.js +103 -0
- package/core/.formation/bifrost/shared/tools.js +278 -0
- package/core/.formation/bifrost/sync.js +142 -0
- package/core/.formation/briefs/scheduler-evolution.md +209 -0
- package/core/.formation/lead.yaml +118 -0
- package/core/.formation/legacy.bak/designer.yaml +57 -0
- package/core/.formation/legacy.bak/pm.yaml +57 -0
- package/core/.formation/legacy.bak/qa.yaml +57 -0
- package/core/.formation/legacy.bak/review.yaml +62 -0
- package/core/.formation/legacy.bak/scheduler.yaml +129 -0
- package/core/.formation/legacy.bak/sre.yaml +57 -0
- package/core/.formation/legion/forge.yaml +299 -0
- package/core/.formation/legion/hive.yaml +238 -0
- package/core/.formation/native/claude/README.md +61 -0
- package/core/.formation/native/claude/models.json +42 -0
- package/core/.formation/role-map.yaml +274 -0
- package/core/.formation/roles/_shared/modules/code_review.yaml +98 -0
- package/core/.formation/roles/_shared/modules/security.yaml +103 -0
- package/core/.formation/roles/_shared/modules/testing.yaml +14 -0
- package/core/.formation/roles/backend/role.yaml +93 -0
- package/core/.formation/roles/code_reviewer/role.yaml +102 -0
- package/core/.formation/roles/database_optimizer/role.yaml +107 -0
- package/core/.formation/roles/designer/role.yaml +66 -0
- package/core/.formation/roles/developer/role.yaml +75 -0
- package/core/.formation/roles/frontend/role.yaml +134 -0
- package/core/.formation/roles/incident_response/role.yaml +89 -0
- package/core/.formation/roles/qa/role.yaml +89 -0
- package/core/.formation/roles/security/role.yaml +87 -0
- package/core/.formation/roles/sre/role.yaml +66 -0
- package/core/.formation/skills/coordinator.yaml +52 -0
- package/core/.formation/solo/diagnosis.yaml +78 -0
- package/core/.formation/solo/strategy.yaml +121 -0
- package/core/.formation/spec/task_operations.parity.test.js +426 -0
- package/core/.formation/spec/task_operations.yaml +291 -0
- package/core/.formation/squad/blitz.yaml +192 -0
- package/core/.formation/squad/strategy-tribunal.yaml +354 -0
- package/core/.formation/squad/strategy.yaml +349 -0
- package/core/.formation/squad/sweep.yaml +208 -0
- package/core/.formation/tests/hello.txt +2 -0
- package/core/.formation/tools/__tests__/m1-claim-gate.test.js +175 -0
- package/core/.formation/tools/__tests__/m1-hmap-convid.test.js +216 -0
- package/core/.formation/tools/__tests__/release-lock.test.js +176 -0
- package/core/.formation/tools/__tests__/subagent-bifrost-guard.test.js +234 -0
- package/core/.formation/tools/__tests__/subagent-codex-harness.test.js +96 -0
- package/core/.formation/tools/__tests__/task-1863-claim-late-release.test.js +160 -0
- package/core/.formation/tools/__tests__/task-2265-singleton-identity.test.js +134 -0
- package/core/.formation/tools/__tests__/task-2281-singleton-readers.test.js +64 -0
- package/core/.formation/tools/__tests__/task2030-worktree-node-modules.test.js +221 -0
- package/core/.formation/tools/__tests__/task2067-worktree-branch-reuse.test.js +148 -0
- package/core/.formation/tools/alaya-gate.js +198 -0
- package/core/.formation/tools/analytics.js +172 -0
- package/core/.formation/tools/broadcast.js +103 -0
- package/core/.formation/tools/commit-sync.js +154 -0
- package/core/.formation/tools/coordinator.js +350 -0
- package/core/.formation/tools/dashboard-models-chain.test.js +60 -0
- package/core/.formation/tools/dispatch-cortex-context.test.js +52 -0
- package/core/.formation/tools/dispatch-detail/cross-project-worktree.md +40 -0
- package/core/.formation/tools/dispatch-detail/testing-module.md +53 -0
- package/core/.formation/tools/dispatch-detail/worktree-subagent-mode.md +72 -0
- package/core/.formation/tools/dispatch-dryrun.test.js +54 -0
- package/core/.formation/tools/dispatch-model-policy.test.js +165 -0
- package/core/.formation/tools/dispatch-notifier.js +84 -0
- package/core/.formation/tools/dispatch-registry.js +346 -0
- package/core/.formation/tools/dispatch.js +1931 -0
- package/core/.formation/tools/formations.js +1203 -0
- package/core/.formation/tools/forseti-verify.js +1170 -0
- package/core/.formation/tools/git-salvage.js +269 -0
- package/core/.formation/tools/guard-attribution.js +224 -0
- package/core/.formation/tools/headless-branch-recovery.test.js +64 -0
- package/core/.formation/tools/headless-dryrun.test.js +79 -0
- package/core/.formation/tools/headless-v2.js +659 -0
- package/core/.formation/tools/headless.js +1071 -0
- package/core/.formation/tools/integration.js +528 -0
- package/core/.formation/tools/jev-correction.js +214 -0
- package/core/.formation/tools/jev-correction.test.js +178 -0
- package/core/.formation/tools/legacy-scheduler.js +1994 -0
- package/core/.formation/tools/lib/exec-tree.js +92 -0
- package/core/.formation/tools/lib/exec-tree.test.js +82 -0
- package/core/.formation/tools/loader.js +190 -0
- package/core/.formation/tools/log-reader.js +232 -0
- package/core/.formation/tools/logger.js +273 -0
- package/core/.formation/tools/mcp-allowlist.test.js +40 -0
- package/core/.formation/tools/memory-loader.js +422 -0
- package/core/.formation/tools/merge-lock-heartbeat.test.js +35 -0
- package/core/.formation/tools/no-hooks.settings.json +3 -0
- package/core/.formation/tools/pool-status-push.js +121 -0
- package/core/.formation/tools/pool-status-push.test.js +165 -0
- package/core/.formation/tools/post-complete/bounded-http.js +102 -0
- package/core/.formation/tools/post-complete/bounded-http.test.js +130 -0
- package/core/.formation/tools/post-complete/cognitive.js +270 -0
- package/core/.formation/tools/post-complete/git-merge.js +2845 -0
- package/core/.formation/tools/post-complete/health.js +464 -0
- package/core/.formation/tools/post-complete/integration-gaps.js +98 -0
- package/core/.formation/tools/post-complete/outcome.js +874 -0
- package/core/.formation/tools/post-complete/staging.js +390 -0
- package/core/.formation/tools/post-complete/subagent-transcript-stats.js +460 -0
- package/core/.formation/tools/post-complete/subagent-transcript-stats.test.js +680 -0
- package/core/.formation/tools/post-complete/task1536.test.js +627 -0
- package/core/.formation/tools/post-complete/task1543.test.js +261 -0
- package/core/.formation/tools/post-complete/task1876.test.js +287 -0
- package/core/.formation/tools/post-complete/task1957.test.js +293 -0
- package/core/.formation/tools/post-complete/task1997.test.js +401 -0
- package/core/.formation/tools/post-complete/task2028.test.js +232 -0
- package/core/.formation/tools/post-complete/task2029.test.js +275 -0
- package/core/.formation/tools/post-complete/task2036.test.js +330 -0
- package/core/.formation/tools/post-complete/task2038.test.js +246 -0
- package/core/.formation/tools/post-complete/task2071.test.js +231 -0
- package/core/.formation/tools/post-complete/task2233.test.js +100 -0
- package/core/.formation/tools/post-complete/task2240.test.js +134 -0
- package/core/.formation/tools/post-complete/task2244.test.js +199 -0
- package/core/.formation/tools/post-complete/task2255.test.js +28 -0
- package/core/.formation/tools/post-complete/task2268.test.js +106 -0
- package/core/.formation/tools/post-complete/task2341.test.js +74 -0
- package/core/.formation/tools/post-complete/task2345.test.js +38 -0
- package/core/.formation/tools/post-complete/testing.js +482 -0
- package/core/.formation/tools/post-complete/wish.js +177 -0
- package/core/.formation/tools/post-complete/worktree-rescue.test.js +283 -0
- package/core/.formation/tools/post-complete.js +3660 -0
- package/core/.formation/tools/post-complete.test.js +1002 -0
- package/core/.formation/tools/pre-dispatch-snapshot.test.js +328 -0
- package/core/.formation/tools/process-state.js +33 -0
- package/core/.formation/tools/prompt.js +2183 -0
- package/core/.formation/tools/prompt.test.js +875 -0
- package/core/.formation/tools/remote-conflict.js +98 -0
- package/core/.formation/tools/revert-daemon-state.js +162 -0
- package/core/.formation/tools/route-audit.test.js +55 -0
- package/core/.formation/tools/runner.js +1332 -0
- package/core/.formation/tools/supabase-client.js +104 -0
- package/core/.formation/tools/task-sync.js +236 -0
- package/core/.formation/tools/task-utils.js +344 -0
- package/core/.formation/tools/task2028-headless-worktree.test.js +112 -0
- package/core/.formation/tools/test-drift.js +148 -0
- package/core/.formation/tools/tests/headless-mcp-args-plumbing.test.js +123 -0
- package/core/.formation/tools/tests/relay-mcp-overrides.test.js +112 -0
- package/core/.formation/tools/transcript-recorder.js +574 -0
- package/core/.formation/tools/utils-claude-cmd.test.js +28 -0
- package/core/.formation/tools/utils-worktree-config.test.js +44 -0
- package/core/.formation/tools/utils.js +1020 -0
- package/core/.formation/tools/verify-acceptance.js +585 -0
- package/core/.formation/tools/worktree-leak.test.js +162 -0
- package/core/.formation/tools/worktree-reclaim.js +259 -0
- package/core/.formation/tools/worktree-reclaim.test.js +182 -0
- package/core/.formation/tools/yggdrasil-local.js +97 -0
- package/core/.formation/unit/do.yaml +78 -0
- package/core/.formation/unit/heimdall.yaml +96 -0
- package/core/.formation/unit/plan.yaml +67 -0
- package/core/.formation/unit/think.yaml +58 -0
- package/core/.kenaz/MUNINN_SNAPSHOT9_EPISODE.json +32 -0
- package/core/.kenaz/MUNINN_SNAPSHOT9_RUNE.json +23 -0
- package/core/.kenaz/MUNINN_SNAPSHOT9_SUMMARY.md +89 -0
- package/core/.kenaz/ash_TASK_1428-20260507.json +14 -0
- package/core/.kenaz/cache/native-models/claude.json +42 -0
- package/core/.kenaz/claude-hooks-manifest.json +119 -0
- package/core/.kenaz/cortex.yaml +100 -0
- package/core/.kenaz/curator_state.json +7 -0
- package/core/.kenaz/dashboard-quick-prompts.json +9 -0
- package/core/.kenaz/designs/METAPLASTICITY_CONFIDENCE_DESIGN.md +537 -0
- package/core/.kenaz/designs/REFLEX_INITIATOR_DESIGN.md +501 -0
- package/core/.kenaz/designs/TASK_1385_handoff_mcp_design.md +426 -0
- package/core/.kenaz/designs/TASK_932_hippocampus_consolidation.md +103 -0
- package/core/.kenaz/designs/TASK_934_efe_signal_engine.md +97 -0
- package/core/.kenaz/designs/TASK_935_self_evolution_framework.md +154 -0
- package/core/.kenaz/designs/VISION_cognitive_architecture_roadmap.md +159 -0
- package/core/.kenaz/designs/metaplasticity_simulation_results.json +696 -0
- package/core/.kenaz/docs/INDEX.md +214 -0
- package/core/.kenaz/docs/README.md +290 -0
- package/core/.kenaz/docs/registry.json +962 -0
- package/core/.kenaz/docs/schema.json +144 -0
- package/core/.kenaz/docs/tools/GIT_HOOK_GUIDE.md +203 -0
- package/core/.kenaz/docs/tools/RAG_INTEGRATION_GUIDE.md +306 -0
- package/core/.kenaz/docs/tools/README.md +99 -0
- package/core/.kenaz/docs/tools/TIME_ESTIMATION_GUIDE.md +299 -0
- package/core/.kenaz/docs/tools/package-lock.json +618 -0
- package/core/.kenaz/docs/tools/package.json +34 -0
- package/core/.kenaz/docs/tools/src/cli.ts +365 -0
- package/core/.kenaz/docs/tools/src/field-extractor.ts +152 -0
- package/core/.kenaz/docs/tools/src/index.ts +14 -0
- package/core/.kenaz/docs/tools/src/registry.ts +664 -0
- package/core/.kenaz/docs/tools/src/search.ts +195 -0
- package/core/.kenaz/docs/tools/src/types.ts +88 -0
- package/core/.kenaz/docs/tools/test-git-hook.sh +79 -0
- package/core/.kenaz/docs/tools/tsconfig.json +20 -0
- package/core/.kenaz/docs/tools/verify.sh +106 -0
- package/core/.kenaz/episodes/SESSION_ansuz-prompt-1785087304784.yaml +68 -0
- package/core/.kenaz/huginn-exploration.json +8 -0
- package/core/.kenaz/knowledge/ladybug_2c51da1e_shadow_file_frame_group.patch +163 -0
- package/core/.kenaz/knowledge/ladybug_30cf85690e_shadow_page_read_during_checkpoint.patch +393 -0
- package/core/.kenaz/knowledge/ladybug_7a17858d3a_node_group_delete_lock.patch +102 -0
- package/core/.kenaz/knowledge/ladybug_926a0ba1_column_checkpoint.patch +412 -0
- package/core/.kenaz/knowledge/ladybug_a346c4c4b6_pk_index_header_pages.patch +144 -0
- package/core/.kenaz/knowledge/ladybug_ba5f38815b_csr_node_group.patch +86 -0
- package/core/.kenaz/main-memory/README.md +230 -0
- package/core/.kenaz/main-memory/kenaz/cortex/001_cortex_overview.md +168 -0
- package/core/.kenaz/main-memory/kenaz/cortex/001_rag_engine_overview.md +30 -0
- package/core/.kenaz/main-memory/kenaz/memory-system/001_three_tier_architecture.md +361 -0
- package/core/.kenaz/main-memory/kenaz/pipeline/001_task_execution_flow.md +117 -0
- package/core/.kenaz/main-memory/kenaz/pipeline/001_task_granularity.md +154 -0
- package/core/.kenaz/main-memory/kenaz/pipeline/002_team_priority.md +51 -0
- package/core/.kenaz/main-memory/kenaz/pipeline/003_feature_id_system.md +46 -0
- package/core/.kenaz/main-memory/kenaz/rules/001_gen_007_self_containment.md +108 -0
- package/core/.kenaz/main-memory/kenaz/rules/001_rule_priority_system.md +121 -0
- package/core/.kenaz/main-memory/kenaz/rules/002_agt_006_pre_check.md +0 -0
- package/core/.kenaz/main-memory/kenaz/rules/003_ppl_002_task_archival.md +48 -0
- package/core/.kenaz/main-memory/kenaz/system/001_dual_track_task_system.md +197 -0
- package/core/.kenaz/main-memory/knowledge/patterns/001_json_bom_issue.md +78 -0
- package/core/.kenaz/main-memory/knowledge/patterns/001_leader_worker_pattern.md +0 -0
- package/core/.kenaz/main-memory/knowledge/patterns/002_scope_path_conflict.md +115 -0
- package/core/.kenaz/main-memory/knowledge/patterns/002_task_dependency_resolution.md +223 -0
- package/core/.kenaz/main-memory/knowledge/patterns/003_question_protocol.md +242 -0
- package/core/.kenaz/main-memory/knowledge/patterns/004_user_insight_value.md +111 -0
- package/core/.kenaz/main-memory/knowledge/patterns/005_file_lock_pattern.md +274 -0
- package/core/.kenaz/main-memory/knowledge/references/001_ppl_rules.md +28 -0
- package/core/.kenaz/main-memory/knowledge/solutions/001_json_bom_issue.md +137 -0
- package/core/.kenaz/main-memory/knowledge/solutions/001_team_invocation.md +0 -0
- package/core/.kenaz/main-memory/knowledge/solutions/002_passive_monitoring.md +0 -0
- package/core/.kenaz/main-memory/knowledge/solutions/003_windows_file_race_condition.md +239 -0
- package/core/.kenaz/main-memory/registry.json +326 -0
- package/core/.kenaz/main-memory/version.yaml +58 -0
- package/core/.kenaz/muninn_episode_write.ps1 +17 -0
- package/core/.kenaz/muninn_snapshot11_rune_candidates.md +63 -0
- package/core/.kenaz/muninn_snapshot1_rune_candidates.md +49 -0
- package/core/.kenaz/orphan_audit_2026-05.md +181 -0
- package/core/.kenaz/ownership.yaml +155 -0
- package/core/.kenaz/peer.yaml +72 -0
- package/core/.kenaz/project.yaml +62 -0
- package/core/.kenaz/rune_ansuz_literature_validation_strategy.md +40 -0
- package/core/.kenaz/rune_efe_active_inference_validation.md +36 -0
- package/core/.kenaz/rune_yggdrasil_filesystem_scoped_project.md +41 -0
- package/core/.kenaz/rune_yggdrasil_project_identity_folder_name.md +41 -0
- package/core/.kenaz/rune_yggdrasil_relay_protocol_project_mismatch.md +39 -0
- package/core/.kenaz/runes_TASK_1428.json +57 -0
- package/core/.kenaz/specialist/ansuz_motivational_state.schema.json +138 -0
- package/core/.kenaz/specialist/ansuz_rules.yaml +241 -0
- package/core/.kenaz/specialist/ansuz_session_handoff.schema.json +128 -0
- package/core/.kenaz/specialist/chart_TASK_1657.json +50 -0
- package/core/.kenaz/specialist/cognitive-hub-e2e.test.js +494 -0
- package/core/.kenaz/specialist/daemon-manifest.yaml +168 -0
- package/core/.kenaz/specialist/empty_ac_audit_report.md +63 -0
- package/core/.kenaz/specialist/episode_muninn_completion_1785056865691.json +91 -0
- package/core/.kenaz/specialist/field_report_watermark.json +1 -0
- package/core/.kenaz/specialist/gc1826_driver.js +118 -0
- package/core/.kenaz/specialist/heimdall_proposal_1017c07f.json +13 -0
- package/core/.kenaz/specialist/heimdall_proposal_3dfaa8b9.json +12 -0
- package/core/.kenaz/specialist/heimdall_proposal_57af18f9.json +15 -0
- package/core/.kenaz/specialist/heimdall_proposal_57b1aa1b.json +14 -0
- package/core/.kenaz/specialist/heimdall_proposal_5a3c9473.json +13 -0
- package/core/.kenaz/specialist/identity_verdicts/TASK_1073.json +75 -0
- package/core/.kenaz/specialist/identity_verdicts/TASK_TEST.json +77 -0
- package/core/.kenaz/specialist/memory_guard_fired.json +1 -0
- package/core/.kenaz/specialist/memory_guard_trigger_cache.json +1 -0
- package/core/.kenaz/specialist/memory_source_priority.yaml +260 -0
- package/core/.kenaz/specialist/motivational_state.schema.json +62 -0
- package/core/.kenaz/specialist/prompt_1775.txt +317 -0
- package/core/.kenaz/specialist/prompt_1776.txt +326 -0
- package/core/.kenaz/specialist/prompt_1791.txt +320 -0
- package/core/.kenaz/specialist/prompt_1792.txt +320 -0
- package/core/.kenaz/specialist/prompt_1795.txt +281 -0
- package/core/.kenaz/specialist/prompt_1813.txt +332 -0
- package/core/.kenaz/specialist/prompt_1814.txt +401 -0
- package/core/.kenaz/specialist/prompt_1815.txt +394 -0
- package/core/.kenaz/specialist/prompt_1816.txt +437 -0
- package/core/.kenaz/specialist/prompt_1818.txt +361 -0
- package/core/.kenaz/specialist/prompt_1819.txt +421 -0
- package/core/.kenaz/specialist/prompt_1821.txt +345 -0
- package/core/.kenaz/specialist/prompt_1822.txt +313 -0
- package/core/.kenaz/specialist/prompt_1824.txt +348 -0
- package/core/.kenaz/specialist/prompt_1826.txt +388 -0
- package/core/.kenaz/specialist/retired_organs/README.md +58 -0
- package/core/.kenaz/specialist/rune_calibration_poc.md +175 -0
- package/core/.kenaz/specialist/rune_efe_salience_gap.md +21 -0
- package/core/.kenaz/specialist/rune_ember_autonomous_dispatch_gate.md +24 -0
- package/core/.kenaz/specialist/rune_post_dispatch_verification_pattern.md +31 -0
- package/core/.kenaz/specialist/rune_surprise_proxy_improvement.md +28 -0
- package/core/.kenaz/specialist/rune_worktree_dispatch_pattern.md +31 -0
- package/core/.kenaz/specialist/rune_yaml_filter_desync_fix.md +26 -0
- package/core/.kenaz/specialist/rune_yggdrasil_project_identity_folder_name.md +24 -0
- package/core/.kenaz/specialist/sessions/chart_TASK_1599.json +41 -0
- package/core/.kenaz/specialist/sessions/hmap_028aa781-aad2-48c0-9a61-5540565d4a7f.json +5 -0
- package/core/.kenaz/specialist/sessions/hmap_20210387-0c64-400c-9d0a-53042a0cbd89.json +5 -0
- package/core/.kenaz/specialist/sessions/hmap_c1375c5d-fd9f-44bd-a83e-353ecbb0392f.json +5 -0
- package/core/.kenaz/specialist/sessions/scratch_028aa781-aad2-48c0-9a61-5540565d4a7f.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_0acc0fb3-24f9-453f-9e92-6ef1cdf03a7c.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_20210387-0c64-400c-9d0a-53042a0cbd89.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_2f10623e-b7a1-4647-905e-9dc7c4d99f9c.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_5a38b62d-608a-488c-82f0-7e47e95e0905.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_5d9758ef-d9b6-4ffe-a017-3ed26d0a313b.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_c1375c5d-fd9f-44bd-a83e-353ecbb0392f.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_c9f3c711-b73d-4fcd-ad2a-7b2bc024ee77.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_e72e5274-1c81-4655-a05a-f87d1ecb2917.json +6 -0
- package/core/.kenaz/specialist/sessions/scratch_f740cdbe-f756-41fc-98fb-2b1413d51901.json +6 -0
- package/core/.kenaz/specialist/t1777_probe.txt +1 -0
- package/core/.kenaz/specialist/task1825_update.json +43 -0
- package/core/.kenaz/specialist/task1827_update.json +43 -0
- package/core/.kenaz/specialist/task1828_update.json +43 -0
- package/core/.kenaz/specialist/taskgen/consolidation_i.json +38 -0
- package/core/.kenaz/specialist/taskgen/csharp_a.json +44 -0
- package/core/.kenaz/specialist/taskgen/csharp_b.json +40 -0
- package/core/.kenaz/specialist/taskgen/embedding_e.json +34 -0
- package/core/.kenaz/specialist/taskgen/hygiene_fg.json +39 -0
- package/core/.kenaz/specialist/taskgen/pathology_h.json +36 -0
- package/core/.kenaz/specialist/taskgen/unity_noise_d.json +38 -0
- package/core/.kenaz/specialist/taskgen/visibility_c.json +38 -0
- package/core/.kenaz/specialist/verify_1837.js +302 -0
- package/core/.kenaz/specialist/verify_1838.js +124 -0
- package/core/.kenaz/tmp/heimdall_proposal_fc7e0944.json +10 -0
- package/core/.kenaz/tmp_heimdall_proposal_8d3e392.json +13 -0
- package/core/.kenaz/tmp_props.json +1 -0
- package/core/.kenaz/tmp_sounddrop_t1_prompt.txt +371 -0
- package/core/.kenaz/user_model.schema.json +107 -0
- package/core/.matrix/README.md +257 -0
- package/core/.matrix/index.matrix +5 -0
- package/core/.mcp.json +8 -0
- package/core/.prefrontal/.gitkeep +0 -0
- package/core/.prefrontal/proposals/.gitkeep +0 -0
- package/core/.rule/INDEX.yaml +99 -0
- package/core/.rule/MECHANIZED.md +61 -0
- package/core/.rule/code/README.md +211 -0
- package/core/.rule/code/backend.yaml +294 -0
- package/core/.rule/code/checklist.yaml +258 -0
- package/core/.rule/code/maintainability.yaml +227 -0
- package/core/.rule/code/naming.yaml +106 -0
- package/core/.rule/code/principles.yaml +71 -0
- package/core/.rule/code/reusability.yaml +192 -0
- package/core/.rule/code/structure.yaml +218 -0
- package/core/.rule/code/style.yaml +197 -0
- package/core/.rule/design/ui.yaml +79 -0
- package/core/.rule/general/agent-workflow.yaml +572 -0
- package/core/.rule/general/cortex-memory-recall.yaml +446 -0
- package/core/.rule/general/cortex-tools.yaml +181 -0
- package/core/.rule/general/cross-platform.yaml +107 -0
- package/core/.rule/general/ddx-synapse-stack.yaml +162 -0
- package/core/.rule/general/file-size-threshold.yaml +45 -0
- package/core/.rule/general/identity-contract.schema.yaml +251 -0
- package/core/.rule/general/memory-language.yaml +67 -0
- package/core/.rule/general/memory-source-priority.yaml +207 -0
- package/core/.rule/general/pipeline.yaml +246 -0
- package/core/.rule/general/principles.yaml +77 -0
- package/core/.rule/general/project-self-containment.yaml +253 -0
- package/core/.rule/general/rust-verify-target.yaml +80 -0
- package/core/.rule/general/specialist-identity-contract.yaml +153 -0
- package/core/.rule/general/sunny-access-control.yaml +60 -0
- package/core/.rule/general/synapse-code-intelligence.yaml +375 -0
- package/core/.rule/general/synapse-cognitive-fusion.yaml +301 -0
- package/core/.rule/general/system-invariants.yaml +248 -0
- package/core/.rule/general/task-pool-routing.yaml +102 -0
- package/core/.rule/general/worktree-isolation.yaml +79 -0
- package/core/.rule/pipeline/PPL-002-completed-task-archival.yaml +528 -0
- package/core/.rule/pipeline/auto-update.yaml +282 -0
- package/core/.rule/pipeline/task-execution.yaml +368 -0
- package/core/.rule/tools/check-permissions.js +197 -0
- package/core/.specialist/Amelia/README.md +329 -0
- package/core/.specialist/Amelia/capability.yaml +386 -0
- package/core/.specialist/Amelia/knowledge.yaml +217 -0
- package/core/.specialist/Amelia/principles.yaml +10 -0
- package/core/.specialist/Amelia/rule.yaml +79 -0
- package/core/.specialist/Amelia/workflow.yaml +241 -0
- package/core/.specialist/Ansuz/HEURISTIC_BOOT_ROUTING.md +39 -0
- package/core/.specialist/Ansuz/ROUTING.md +35 -0
- package/core/.specialist/Ansuz/RULES.md +79 -0
- package/core/.specialist/Ansuz/SYSTEM_DOC.md +611 -0
- package/core/.specialist/Ansuz/VERIFY.md +38 -0
- package/core/.specialist/Ansuz/capability.yaml +338 -0
- package/core/.specialist/Ansuz/cognitive_principles.yaml +62 -0
- package/core/.specialist/Ansuz/concepts/agent_types.yaml +22 -0
- package/core/.specialist/Ansuz/concepts/autonomous_agenda.yaml +170 -0
- package/core/.specialist/Ansuz/concepts/autonomy_contract.yaml +126 -0
- package/core/.specialist/Ansuz/concepts/boot_ritual.yaml +107 -0
- package/core/.specialist/Ansuz/concepts/boot_rules_summary.yaml +16 -0
- package/core/.specialist/Ansuz/concepts/capability_pointers.yaml +21 -0
- package/core/.specialist/Ansuz/concepts/cognitive_fluency_contract.yaml +264 -0
- package/core/.specialist/Ansuz/concepts/cognitive_systems_competence_map.yaml +14 -0
- package/core/.specialist/Ansuz/concepts/cognitive_systems_hippocampus.yaml +14 -0
- package/core/.specialist/Ansuz/concepts/cognitive_systems_overview.yaml +27 -0
- package/core/.specialist/Ansuz/concepts/cognitive_systems_serendipity.yaml +14 -0
- package/core/.specialist/Ansuz/concepts/cognitive_systems_turn_context.yaml +13 -0
- package/core/.specialist/Ansuz/concepts/commitment_layer.yaml +150 -0
- package/core/.specialist/Ansuz/concepts/completion_tracking.yaml +29 -0
- package/core/.specialist/Ansuz/concepts/deep_knowledge_pointers.yaml +28 -0
- package/core/.specialist/Ansuz/concepts/evolvable_guardrails.yaml +20 -0
- package/core/.specialist/Ansuz/concepts/fatigue_handoff.yaml +84 -0
- package/core/.specialist/Ansuz/concepts/formation_quick_ref.yaml +28 -0
- package/core/.specialist/Ansuz/concepts/guardrails_immutable.yaml +31 -0
- package/core/.specialist/Ansuz/concepts/identity.yaml +48 -0
- package/core/.specialist/Ansuz/concepts/interaction_protocol.yaml +42 -0
- package/core/.specialist/Ansuz/concepts/meta_evolvable_tier.yaml +28 -0
- package/core/.specialist/Ansuz/concepts/mindset.yaml +18 -0
- package/core/.specialist/Ansuz/concepts/phase9_verification.yaml +21 -0
- package/core/.specialist/Ansuz/concepts/phase_protocol.yaml +66 -0
- package/core/.specialist/Ansuz/concepts/principles.yaml +65 -0
- package/core/.specialist/Ansuz/concepts/self_evolution_overview.yaml +53 -0
- package/core/.specialist/Ansuz/concepts/task_first_protocol.yaml +7 -0
- package/core/.specialist/Ansuz/concepts/task_pool.yaml +119 -0
- package/core/.specialist/Ansuz/concepts/task_system.yaml +22 -0
- package/core/.specialist/Ansuz/concepts/write_verification.yaml +26 -0
- package/core/.specialist/Ansuz/core.yaml +224 -0
- package/core/.specialist/Ansuz/decisions/ADR-requirement-graph-layer.md +390 -0
- package/core/.specialist/Ansuz/elixir.yaml +70 -0
- package/core/.specialist/Ansuz/exo_cortex_tools.yaml +13 -0
- package/core/.specialist/Ansuz/experiments/boot_ablation_replay.js +197 -0
- package/core/.specialist/Ansuz/guards/blind_spot_guards.yaml +28 -0
- package/core/.specialist/Ansuz/guards/lesson_guards.yaml +203 -0
- package/core/.specialist/Ansuz/identity_contract.yaml +375 -0
- package/core/.specialist/Ansuz/knowledge.yaml +284 -0
- package/core/.specialist/Ansuz/modules/amelia_review.yaml +112 -0
- package/core/.specialist/Ansuz/modules/ddx.yaml +47 -0
- package/core/.specialist/Ansuz/modules/design_checks.yaml +47 -0
- package/core/.specialist/Ansuz/modules/identity_verification.yaml +119 -0
- package/core/.specialist/Ansuz/modules/loop_guard.yaml +36 -0
- package/core/.specialist/Ansuz/modules/prevention.yaml +84 -0
- package/core/.specialist/Ansuz/modules/prophecy.yaml +95 -0
- package/core/.specialist/Ansuz/modules/pulse_empty.yaml +36 -0
- package/core/.specialist/Ansuz/modules/qa_intake.yaml +29 -0
- package/core/.specialist/Ansuz/modules/recall.yaml +45 -0
- package/core/.specialist/Ansuz/principles.yaml +31 -0
- package/core/.specialist/Ansuz/research/EMBER_architecture_comparison.md +142 -0
- package/core/.specialist/Ansuz/rule.yaml +97 -0
- package/core/.specialist/Ansuz/self_model_seed.backup.json +100 -0
- package/core/.specialist/Ansuz/self_model_seed.json +100 -0
- package/core/.specialist/Ansuz/self_model_seed.sha256 +1 -0
- package/core/.specialist/Ansuz/tools/ansuz-handoff-snapshot.js +626 -0
- package/core/.specialist/Ansuz/tools/ansuz-handoff.js +437 -0
- package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.js +1254 -0
- package/core/.specialist/Ansuz/tools/ansuz-sdk-runner.test.js +137 -0
- package/core/.specialist/Ansuz/tools/ansuz-state.js +288 -0
- package/core/.specialist/Ansuz/tools/bootstrap-anchors.js +289 -0
- package/core/.specialist/Ansuz/tools/handoff-freshness-milestone.test.js +73 -0
- package/core/.specialist/Ansuz/tools/handoff-resolver.js +207 -0
- package/core/.specialist/Ansuz/tools/handoff-session-identity.test.js +333 -0
- package/core/.specialist/Ansuz/tools/handoff-snapshot-args.test.js +25 -0
- package/core/.specialist/Ansuz/tools/install-claude-hooks.js +354 -0
- package/core/.specialist/Ansuz/tools/install-claude-hooks.test.js +110 -0
- package/core/.specialist/Ansuz/tools/package.json +16 -0
- package/core/.specialist/Ansuz/tools/preservation-coordinator.js +504 -0
- package/core/.specialist/Ansuz/tools/verify-and-land.js +1012 -0
- package/core/.specialist/Ansuz/tools/verify-and-land.test.js +1099 -0
- package/core/.specialist/Ansuz/workflow.yaml +1273 -0
- package/core/.specialist/Forseti/capability.yaml +210 -0
- package/core/.specialist/Forseti/core.yaml +176 -0
- package/core/.specialist/Forseti/principles.yaml +88 -0
- package/core/.specialist/Forseti/rule.yaml +86 -0
- package/core/.specialist/Heimdall/SKILL.md +65 -0
- package/core/.specialist/Heimdall/capability.yaml +134 -0
- package/core/.specialist/Heimdall/core.yaml +275 -0
- package/core/.specialist/Heimdall/principles.yaml +66 -0
- package/core/.specialist/Heimdall/rule.yaml +68 -0
- package/core/.specialist/Huginn/capability.yaml +234 -0
- package/core/.specialist/Huginn/core.yaml +482 -0
- package/core/.specialist/Huginn/principles.yaml +57 -0
- package/core/.specialist/Huginn/rule.yaml +121 -0
- package/core/.specialist/REFLECTION_TO_CONTROL.md +347 -0
- package/core/.specialist/ansuz_session_handoff.json +96 -0
- package/core/.specialist/experiments/rg_benchmark.js +523 -0
- package/core/.specialist/experiments/rg_benchmark_results.json +27 -0
- package/core/.specialist/shared/cognitive-primer.md +15 -0
- package/core/.specialist/shared/guard_schema.json +290 -0
- package/core/.specialist/shared/principles.yaml +213 -0
- package/core/.specialist/tests/runtime-discovery/README.md +224 -0
- package/core/.specialist/tests/runtime-discovery/active-project-polling.test.js +495 -0
- package/core/.specialist/tests/runtime-discovery/port-binding.test.js +418 -0
- package/core/.specialist/tests/runtime-discovery/resolver-priority.test.js +693 -0
- package/core/.specialist/tests/runtime-discovery/resolver-skip-on-invalid.test.js +467 -0
- package/core/.specialist/tests/runtime-discovery/resolver-symmetry.test.js +679 -0
- package/core/.specialist/tests/runtime-discovery/tail-cli-project-path.test.js +231 -0
- package/core/.specialist/tools/MEMORY_RECALL_TRIGGERS.md +156 -0
- package/core/.specialist/tools/README.md +129 -0
- package/core/.specialist/tools/__tests__/freya-cli-contract.test.js +156 -0
- package/core/.specialist/tools/__tests__/task-1863-boot-lease-reuse.test.js +99 -0
- package/core/.specialist/tools/__tests__/task-1917-orphan-detection.test.js +175 -0
- package/core/.specialist/tools/__tests__/task-1977-banner-cap.test.js +129 -0
- package/core/.specialist/tools/__tests__/task-1983-lease-project-path.test.js +96 -0
- package/core/.specialist/tools/__tests__/task-2218-boot-session-id.test.js +118 -0
- package/core/.specialist/tools/__tests__/task-2222-session-identity-interleave.test.js +203 -0
- package/core/.specialist/tools/__tests__/task-2246-reap-honesty.test.js +96 -0
- package/core/.specialist/tools/__tests__/task-2254-handoff-archive-contract.test.js +60 -0
- package/core/.specialist/tools/__tests__/task-2272-open-card-status-quote.test.js +26 -0
- package/core/.specialist/tools/__tests__/task-2374-usage-counter.test.js +34 -0
- package/core/.specialist/tools/ansuz-boot-fast.js +1120 -0
- package/core/.specialist/tools/ansuz-boot-fast.test.js +705 -0
- package/core/.specialist/tools/canary.js +501 -0
- package/core/.specialist/tools/context-injection.js +395 -0
- package/core/.specialist/tools/design-reasoning.js +493 -0
- package/core/.specialist/tools/discovery-fixtures.js +308 -0
- package/core/.specialist/tools/discovery-runner.js +460 -0
- package/core/.specialist/tools/failure-detector.js +1132 -0
- package/core/.specialist/tools/identity-scorer.js +714 -0
- package/core/.specialist/tools/jev-optional-context.js +107 -0
- package/core/.specialist/tools/jev-optional-context.test.js +152 -0
- package/core/.specialist/tools/lesson-guard.js +1084 -0
- package/core/.specialist/tools/lib/chart-path.js +159 -0
- package/core/.specialist/tools/lib/chart-path.test.js +171 -0
- package/core/.specialist/tools/lib/project-resolver.js +200 -0
- package/core/.specialist/tools/lib/project-resolver.test.js +241 -0
- package/core/.specialist/tools/memory-action-log.js +839 -0
- package/core/.specialist/tools/phase-gate.js +3754 -0
- package/core/.specialist/tools/phase-gate.test.js +85 -0
- package/core/.specialist/tools/phase9-policy.test.js +109 -0
- package/core/.specialist/tools/precompute-runner.js +444 -0
- package/core/.specialist/tools/prompt-files.test.js +169 -0
- package/core/.specialist/tools/replay-detectors.js +290 -0
- package/core/.specialist/tools/rule-engine.js +621 -0
- package/core/.specialist/tools/session-boot-ownership.test.js +117 -0
- package/core/.specialist/tools/session-project-state.test.js +65 -0
- package/core/.specialist/tools/session.js +1866 -0
- package/core/.specialist/tools/stream-lease.js +263 -0
- package/core/.specialist/tools/task-bridge-jev.test.js +170 -0
- package/core/.specialist/tools/task-bridge.js +765 -0
- package/core/.specialist/tools/verify-identity.js +453 -0
- package/core/.specialist/tools/wish-mandate-delivery.test.js +76 -0
- package/core/.system/README.md +555 -0
- package/core/.system/agent-skills/README.md +724 -0
- package/core/.system/archive/README.md +499 -0
- package/core/.system/assessment/README.md +645 -0
- package/core/.system/cli-tools/README.md +724 -0
- package/core/.system/collaboration/KNOWLEDGE.md +65 -0
- package/core/.system/collaboration/README.md +570 -0
- package/core/.system/cortex/MODULES.md +1046 -0
- package/core/.system/dashboard/FRONTEND.md +812 -0
- package/core/.system/dashboard/README.md +737 -0
- package/core/.system/external-skills/README.md +522 -0
- package/core/.system/failure/README.md +723 -0
- package/core/.system/formation/LEGACY_SKILLS.md +972 -0
- package/core/.system/formation/POST_COMPLETE.md +600 -0
- package/core/.system/formation/README.md +1092 -0
- package/core/.system/formation/TOOLS_REFERENCE.md +1145 -0
- package/core/.system/githooks/README.md +846 -0
- package/core/.system/kenaz/README.md +787 -0
- package/core/.system/memory/README.md +716 -0
- package/core/.system/pipeline/README.md +712 -0
- package/core/.system/rule/README.md +794 -0
- package/core/.system/specialist/README.md +669 -0
- package/core/.system/specialist/STRUCTURE.md +262 -0
- package/core/.system/sunny/README.md +476 -0
- package/core/.system/sunny/RULES.json +89 -0
- package/core/.system/sunny/knowledge/INDEX.md +37 -0
- package/core/.system/sunny/knowledge/agent-teams-api.md +0 -0
- package/core/.system/sunny/knowledge/ansuz-specialist.md +500 -0
- package/core/.system/sunny/knowledge/claude-cli-spawn.md +177 -0
- package/core/.system/sunny/knowledge/collaboration-api.md +925 -0
- package/core/.system/sunny/teammates/README.md +107 -0
- package/core/.system/sunny/teammates/how_to_use.yaml +101 -0
- package/core/.system/sunny/teammates/tools/headless/launcher.js +318 -0
- package/core/.system/sunny/teammates/tools/powershell/launcher.js +241 -0
- package/core/.system/sunny/teammates/tools/powershell/start.bat +50 -0
- package/core/.system/sunny/teammates/tools/utils/env.js +96 -0
- package/core/.system/sunny/teammates/tools/utils/format-output.js +329 -0
- package/core/.system/sunny/tools.json +101 -0
- package/core/.vendor/js-yaml.cjs +2795 -0
- package/core/.vendor/require-yaml.cjs +8 -0
- package/core/CLAUDE.md +172 -0
- package/core/KENAZ_CORE_VERSION +1 -0
- package/core/dev/ALAYA_FIELD_FEEDBACK_DESIGN.md +126 -0
- package/core/dev/ANSUZ_SCAFFOLD_DIET_2026-09-20.md +231 -0
- package/core/dev/ANSUZ_STARTUP_INJECTION_REVIEW.md +355 -0
- package/core/dev/ANSUZ_WORKFLOW_MODERNIZATION.md +143 -0
- package/core/dev/CORTEX_META_GRAPH_ASSESSMENT.md +99 -0
- package/core/dev/FREYA_DOWNSTREAM_BUGS_2026-09-19.md +44 -0
- package/core/dev/GLOBAL_MEMORY_IDENTITY_CONSOLIDATION.md +263 -0
- package/core/dev/GUARD_EFFECTIVENESS_DESIGN.md +180 -0
- package/core/dev/HEALTH_GATE_VARIANCE_ASSESSMENT.md +271 -0
- package/core/dev/HOOK_DIET_2026-09-21.md +126 -0
- package/core/dev/JEV_INTEGRATION_PLAN.md +189 -0
- package/core/dev/KENAZ_AGENT_CONNECT_v1_SPEC.md +139 -0
- package/core/dev/MOBILE_DESIGN_SPEC.md +569 -0
- package/core/dev/MULTI_ANSUZ_SPEC.md +136 -0
- package/core/dev/MUNINN_RETIREMENT.md +115 -0
- package/core/dev/PHASE3_ATTRIBUTION_POPULATION_DIAGNOSIS.md +211 -0
- package/core/dev/PHASE3_P3-2B_TASKLEVEL_PROBE.md +83 -0
- package/core/dev/PHASE3_P3-2_POPULATION_DISJOINTNESS.md +83 -0
- package/core/dev/PHASE3_P3-2_REMEASURE_REPORT.md +231 -0
- package/core/dev/PREDICTION_GROUNDING_DESIGN.md +376 -0
- package/core/dev/PRODUCT_SPEC.md +317 -0
- package/core/dev/PRODUCT_SPEC_FOR_AI_tepmlate.md +716 -0
- package/core/dev/PROJECT_REGISTRY_DUPLICATE_ASSESSMENT.md +86 -0
- package/core/dev/STARTUP_TIMELINE_TASK_1995.md +181 -0
- package/core/dev/SYNAPSE_WORKER_PER_PROJECT.md +881 -0
- package/core/dev/TASK_1680_P3-3_DISPATCH_SPEC.md +96 -0
- package/core/dev/TASK_1684_CALIBRATION_IMPACT_ANALYSIS.md +170 -0
- package/core/dev/TASK_1684_FORK_A_GOODHART_DESIGN.md +171 -0
- package/core/dev/TASK_1684_P3_2_REMEASURE_VALIDATION.md +262 -0
- package/core/dev/TASK_1903_SIGNAL_AUC_MEASUREMENT.md +207 -0
- package/core/dev/TASK_1904_BRANCH_INTEGRITY_AUDIT.md +146 -0
- package/core/dev/ansuz-eval/README.md +38 -0
- package/core/dev/ansuz-eval/build-fixtures.js +87 -0
- package/core/dev/ansuz-eval/grade.js +96 -0
- package/core/dev/ansuz-eval/grader.test.js +117 -0
- package/core/dev/ansuz-eval/tasks.md +32 -0
- package/core/dev/archive/ansuz/BOOT_FULL.md +141 -0
- package/core/dev/archive/ansuz/agents/ansuz-boot.md +176 -0
- package/core/dev/archive/ansuz/designs/alaya-passive-surfacing.md +84 -0
- package/core/dev/archive/ansuz/designs/cc_memory_indexing.md +307 -0
- package/core/dev/archive/ansuz/designs/check_guard.md +322 -0
- package/core/dev/archive/ansuz/designs/consequence_loop.md +227 -0
- package/core/dev/archive/ansuz/designs/mcp_kuzudb_lock_architecture.md +312 -0
- package/core/dev/archive/ansuz/designs/phase0_git_enrichment.md +278 -0
- package/core/dev/archive/ansuz/designs/refactor_mcp_tools_synapse.md +490 -0
- package/core/dev/archive/ansuz/designs/semantic_markdown_extraction.md +167 -0
- package/core/dev/archive/ansuz/designs/task_1372_prompt_injects_into.md +193 -0
- package/core/dev/archive/ansuz/designs/thought_stack.md +286 -0
- package/core/dev/archive/ansuz/investigations/reachability_unified_api_2026-04-11.md +330 -0
- package/core/dev/archive/ansuz/investigations/synapse_orphans_indexing_bug_2026-04-11.md +352 -0
- package/core/dev/archive/ansuz/investigations/synapse_orphans_reindex_verification_2026-04-11.md +48 -0
- package/core/dev/archive/ansuz/investigations/synapse_orphans_verification_2026-04-11.md +170 -0
- package/core/dev/archive/ansuz/legacy/kernel.yaml +399 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_0.yaml +418 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_1.yaml +376 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_10.yaml +35 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_11.yaml +582 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_2.yaml +94 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_3.yaml +274 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_4.yaml +67 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_5.yaml +32 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_6.yaml +91 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_7.yaml +58 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_8.yaml +146 -0
- package/core/dev/archive/ansuz/legacy/phases/phase_9.yaml +424 -0
- package/core/dev/archive/ansuz/specs/INDEX_bifrost_hook_parity.yaml +92 -0
- package/core/dev/archive/ansuz/specs/active-inference-formation.yaml +453 -0
- package/core/dev/archive/ansuz/specs/brief_TASK_1228_userpromptsubmit.yaml +191 -0
- package/core/dev/archive/ansuz/specs/brief_TASK_1229_pretooluse_posttooluse.yaml +250 -0
- package/core/dev/archive/ansuz/specs/brief_TASK_1230_sessionstart_stop.yaml +301 -0
- package/core/dev/archive/ansuz/specs/brief_TASK_1231_verification.yaml +272 -0
- package/core/dev/design/PHASE4_DESIGN.md +559 -0
- package/core/dev/design/dashboard_docs_integration_PLAN.md +388 -0
- package/core/dev/design/matrix_format_ABBREVIATIONS.md +655 -0
- package/core/dev/design/matrix_format_DESIGN_SPEC.md +601 -0
- package/core/dev/design/matrix_format_DISCUSSION_SUMMARY.md +484 -0
- package/core/dev/design/matrix_format_EXAMPLE_memory.matrix +13 -0
- package/core/dev/design/matrix_format_EXAMPLE_project_status.matrix +9 -0
- package/core/dev/design/matrix_format_EXAMPLE_tasks.matrix +14 -0
- package/core/dev/design/matrix_format_SYNTAX_BNF.md +754 -0
- package/core/dev/design/matrix_format_TOKEN_COMPARISON.md +484 -0
- package/core/dev/design/matrix_v2_COGNITIVE_DESIGN.md +503 -0
- package/core/dev/design/matrix_v2_DISCUSSION_SUMMARY.md +414 -0
- package/core/dev/design/matrix_v2_EXAMPLE.matrix +67 -0
- package/core/dev/designs/EXPANDABLE_CARD_DESIGN_SPEC.md +509 -0
- package/core/dev/jev-eval/ACTIVE_CORRECTION.md +52 -0
- package/core/dev/jev-eval/ANNOTATION_GUIDE.md +68 -0
- package/core/dev/jev-eval/BASELINE_COMPARISON.md +45 -0
- package/core/dev/jev-eval/CONTEXT_REVIEW.md +31 -0
- package/core/dev/jev-eval/EVALUATION_STATUS.md +39 -0
- package/core/dev/jev-eval/HISTORICAL_CORPUS.md +33 -0
- package/core/dev/jev-eval/PACKAGING_VERIFICATION.md +42 -0
- package/core/dev/jev-eval/README.md +101 -0
- package/core/dev/jev-eval/THALAMUS_RANKING.md +58 -0
- package/core/dev/jev-eval/VALIDATION_PROGRESS.md +54 -0
- package/core/dev/jev-eval/cli.mjs +50 -0
- package/core/dev/jev-eval/context.mjs +183 -0
- package/core/dev/jev-eval/corpus.mjs +186 -0
- package/core/dev/jev-eval/fixtures/conversation-review.seed.jsonl +6 -0
- package/core/dev/jev-eval/fixtures/synthetic.jsonl +11 -0
- package/core/dev/jev-eval/metrics.mjs +67 -0
- package/core/dev/jev-eval/muninn-baseline.mjs +119 -0
- package/core/dev/jev-eval/providers.mjs +95 -0
- package/core/dev/jev-eval/reports/annotation-pilot-2026-09-20.json +32 -0
- package/core/dev/jev-eval/reports/context-review-2026-09-20.json +48 -0
- package/core/dev/jev-eval/reports/conversation-review.pending.json +151 -0
- package/core/dev/jev-eval/reports/history-corpus-2026-09-20.json +52 -0
- package/core/dev/jev-eval/reports/length-probe-2026-09-20.json +133 -0
- package/core/dev/jev-eval/reports/live-smoke-2026-09-20.json +616 -0
- package/core/dev/jev-eval/reports/muninn-baseline-2026-09-20.json +52 -0
- package/core/dev/jev-eval/reports/resilience-2026-09-20.json +520 -0
- package/core/dev/jev-eval/reports/rules-baseline.json +197 -0
- package/core/dev/jev-eval/reports/synthetic-review.pending.json +357 -0
- package/core/dev/jev-eval/resilience.mjs +71 -0
- package/core/dev/jev-eval/review.mjs +78 -0
- package/core/dev/jev-eval/schema.mjs +87 -0
- package/core/dev/jev-eval/test/context.test.mjs +95 -0
- package/core/dev/jev-eval/test/corpus.test.mjs +102 -0
- package/core/dev/jev-eval/test/eval.test.mjs +146 -0
- package/core/dev/jev-eval/test/muninn-baseline.test.mjs +71 -0
- package/core/dev/jev-eval/test/resilience.test.mjs +72 -0
- package/core/dev/jev-eval/test/review.test.mjs +61 -0
- package/core/dev/pipeline/LOCK_STATE.json +4 -0
- package/core/dev/pipeline/OPTIMIZATION_PROPOSALS.json +32 -0
- package/core/dev/pipeline/PHASE3_PLANNING_SUMMARY.md +338 -0
- package/core/dev/pipeline/PIPELINE_CONFIG.json +46 -0
- package/core/dev/pipeline/PROGRESS.md +113 -0
- package/core/dev/pipeline/PROGRESS_template.md +860 -0
- package/core/dev/pipeline/QA_REPORT_F008.md +431 -0
- package/core/dev/pipeline/QA_REPORT_LOCAL_CLAUDE_DIAGNOSIS.md +583 -0
- package/core/dev/pipeline/README.md +210 -0
- package/core/dev/pipeline/SELF_ENHANCEMENT_STATUS.json.bak +47 -0
- package/core/dev/pipeline/TASK_QUEUE.json +192 -0
- package/core/dev/pipeline/WORKFLOW_GUIDE.md +824 -0
- package/core/dev/pipeline/archived/TASK_015_tauri_init.yaml +53 -0
- package/core/dev/pipeline/archived/TASK_016_tauri_commands.yaml +54 -0
- package/core/dev/pipeline/archived/TASK_017_rust_pty.yaml +54 -0
- package/core/dev/pipeline/archived/TASK_018_tauri_bundle.yaml +53 -0
- package/core/dev/pipeline/archived/TASK_019_release_workflow.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_020_update_ui.yaml +54 -0
- package/core/dev/pipeline/archived/TASK_021_update_notification_ui.yaml +55 -0
- package/core/dev/pipeline/archived/TASK_022_remove_node_pty.yaml +65 -0
- package/core/dev/pipeline/archived/TASK_023_api_routes_migration.yaml +86 -0
- package/core/dev/pipeline/archived/TASK_024_feature_id_system.yaml +51 -0
- package/core/dev/pipeline/archived/TASK_025_executable_acceptance.yaml +66 -0
- package/core/dev/pipeline/archived/TASK_026_spec_versioning.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_027_drift_detection.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_028_tauri_pty_manager.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_029_tauri_pty_commands.yaml +62 -0
- package/core/dev/pipeline/archived/TASK_030_frontend_session_adapter.yaml +54 -0
- package/core/dev/pipeline/archived/TASK_031_terminal_tauri_events.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_041_scheduler_data_models.yaml +54 -0
- package/core/dev/pipeline/archived/TASK_042_path_lock_manager.yaml +53 -0
- package/core/dev/pipeline/archived/TASK_043_process_manager.yaml +57 -0
- package/core/dev/pipeline/archived/TASK_044_git_integration.yaml +85 -0
- package/core/dev/pipeline/archived/TASK_045_task_queue_manager.yaml +54 -0
- package/core/dev/pipeline/archived/TASK_046_scheduler_dispatcher.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_047_tauri_scheduler_commands.yaml +55 -0
- package/core/dev/pipeline/archived/TASK_048_realtime_status_push.yaml +92 -0
- package/core/dev/pipeline/archived/TASK_049_frontend_scheduler_integration.yaml +58 -0
- package/core/dev/pipeline/archived/TASK_050_error_recovery.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_051_optimization_proposals_backend.yaml +53 -0
- package/core/dev/pipeline/archived/TASK_052_optimization_proposals_ui.yaml +58 -0
- package/core/dev/pipeline/archived/TASK_053_optimization_proposals_diff_view.yaml +54 -0
- package/core/dev/pipeline/archived/TASK_054_multi_project_scheduler_state.yaml +76 -0
- package/core/dev/pipeline/archived/TASK_055_scheduler_api_project_scope.yaml +64 -0
- package/core/dev/pipeline/archived/TASK_056_frontend_scheduler_project_scope.yaml +62 -0
- package/core/dev/pipeline/archived/TASK_057_scheduler_module_structure.yaml +54 -0
- package/core/dev/pipeline/archived/TASK_058_scheduler_models_extract.yaml +51 -0
- package/core/dev/pipeline/archived/TASK_059_scheduler_core_extract.yaml +51 -0
- package/core/dev/pipeline/archived/TASK_060_scheduler_commands_extract.yaml +51 -0
- package/core/dev/pipeline/archived/TASK_061_scheduler_specialized_commands.yaml +53 -0
- package/core/dev/pipeline/archived/TASK_062_process_manager_module_structure.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_063_process_manager_core_extract.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_064_recovery_module_structure.yaml +57 -0
- package/core/dev/pipeline/archived/TASK_065_recovery_core_extract.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_066_self_enhancement_scheduler_models.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_067_agents_section_quick_actions.yaml +53 -0
- package/core/dev/pipeline/archived/TASK_067_self_enhancement_scheduler_core.yaml +65 -0
- package/core/dev/pipeline/archived/TASK_068_agent_workflow_rule_enhancement.yaml +46 -0
- package/core/dev/pipeline/archived/TASK_068_self_enhancement_tauri_commands.yaml +61 -0
- package/core/dev/pipeline/archived/TASK_069_agent_wizard_responsive.yaml +46 -0
- package/core/dev/pipeline/archived/TASK_069_self_enhancement_ui.yaml +62 -0
- package/core/dev/pipeline/archived/TASK_070_preview_panel_redesign.yaml +62 -0
- package/core/dev/pipeline/archived/TASK_070_self_enhancement_integration.yaml +57 -0
- package/core/dev/pipeline/archived/TASK_071_preview_panel_qa.yaml +98 -0
- package/core/dev/pipeline/archived/TASK_071_single_agent_config.yaml +57 -0
- package/core/dev/pipeline/archived/TASK_072_preview_modal_esc_fix.yaml +71 -0
- package/core/dev/pipeline/archived/TASK_072_product_review_module.yaml +57 -0
- package/core/dev/pipeline/archived/TASK_073_agents_page_component_refactor.yaml +68 -0
- package/core/dev/pipeline/archived/TASK_073_rename_single_agent.yaml +64 -0
- package/core/dev/pipeline/archived/TASK_074_preview_mode_ui_diagnosis.yaml +71 -0
- package/core/dev/pipeline/archived/TASK_074_single_agent_ui_toggle.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_075_agents_page_component_refactor.yaml +69 -0
- package/core/dev/pipeline/archived/TASK_075_single_agent_integration_test.yaml +75 -0
- package/core/dev/pipeline/archived/TASK_076_preview_mode_ui_diagnosis.yaml +70 -0
- package/core/dev/pipeline/archived/TASK_076_single_agent_continuous_mode.yaml +65 -0
- package/core/dev/pipeline/archived/TASK_077_failure_directory_structure.yaml +51 -0
- package/core/dev/pipeline/archived/TASK_077_single_agent_typescript_types.yaml +59 -0
- package/core/dev/pipeline/archived/TASK_078_fail_001_agent_no_output.yaml +53 -0
- package/core/dev/pipeline/archived/TASK_078_single_agent_ui_simplify.yaml +74 -0
- package/core/dev/pipeline/archived/TASK_079_dashboard_rwd_responsive.yaml +63 -0
- package/core/dev/pipeline/archived/TASK_079_fail_002_json_bom.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_080_fail_003_queue_pollution.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_080_http_server_module.yaml +55 -0
- package/core/dev/pipeline/archived/TASK_081_failure_logging_tools.yaml +57 -0
- package/core/dev/pipeline/archived/TASK_081_http_api_routes.yaml +62 -0
- package/core/dev/pipeline/archived/TASK_082_frontend_api_adapter.yaml +51 -0
- package/core/dev/pipeline/archived/TASK_082_integrate_failure_to_workflow.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_083_cleanup_nextjs_api.yaml +48 -0
- package/core/dev/pipeline/archived/TASK_083_task_queue_cleanup.yaml +67 -0
- package/core/dev/pipeline/archived/TASK_084_rust_dead_code_cleanup.yaml +66 -0
- package/core/dev/pipeline/archived/TASK_084_scheduler_sync_logic_fix.yaml +68 -0
- package/core/dev/pipeline/archived/TASK_085_http_api_web_mode_fixes.yaml +46 -0
- package/core/dev/pipeline/archived/TASK_085_memory_consolidation_0204_0205.yaml +98 -0
- package/core/dev/pipeline/archived/TASK_086_collapsible_preview_panel.yaml +161 -0
- package/core/dev/pipeline/archived/TASK_086_web_mode_feature_parity.yaml +62 -0
- package/core/dev/pipeline/archived/TASK_087_manual_memory_archival.yaml +163 -0
- package/core/dev/pipeline/archived/TASK_087_mobile_http_control.yaml +68 -0
- package/core/dev/pipeline/archived/TASK_088_fail_003_to_ppl_002_rule.yaml +63 -0
- package/core/dev/pipeline/archived/TASK_088_mobile_http_qa.yaml +121 -0
- package/core/dev/pipeline/archived/TASK_089_agent_collaboration_topic.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_089_web_mode_monitor_fix.yaml +58 -0
- package/core/dev/pipeline/archived/TASK_090_cli_detection_fix.yaml +61 -0
- package/core/dev/pipeline/archived/TASK_090_ui_ux_lessons_topic.yaml +63 -0
- package/core/dev/pipeline/archived/TASK_091_expandable_card_design.yaml +56 -0
- package/core/dev/pipeline/archived/TASK_091_move_agent_section_components.yaml +65 -0
- package/core/dev/pipeline/archived/TASK_092_expandable_card_component.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_092_refactor_agent_create_wizard.yaml +84 -0
- package/core/dev/pipeline/archived/TASK_093_taskcard_detail_integration.yaml +57 -0
- package/core/dev/pipeline/archived/TASK_093_update_barrel_exports_test.yaml +84 -0
- package/core/dev/pipeline/archived/TASK_094_ai_agent_generation_enhancement.yaml +115 -0
- package/core/dev/pipeline/archived/TASK_094_proposalcard_detail_integration.yaml +55 -0
- package/core/dev/pipeline/archived/TASK_095_preview_fixes.yaml +129 -0
- package/core/dev/pipeline/archived/TASK_095_single_agent_task_selector.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_096_agents_page_ux_improvements.yaml +163 -0
- package/core/dev/pipeline/archived/TASK_096_single_agent_status_sync.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_097_local_claude_error_logging.yaml +59 -0
- package/core/dev/pipeline/archived/TASK_097_single_agent_monitor_events.yaml +59 -0
- package/core/dev/pipeline/archived/TASK_098_product_manager_team_phase2.yaml +60 -0
- package/core/dev/pipeline/archived/TASK_098_single_agent_refactor_test.yaml +58 -0
- package/core/dev/pipeline/archived/TASK_099_backend_team_creation.yaml +258 -0
- package/core/dev/pipeline/archived/TASK_100_frontend_team_creation.yaml +61 -0
- package/core/dev/pipeline/archived/TASK_101_orchestrator_system.yaml +403 -0
- package/core/dev/pipeline/archived/TASK_102_designer_sre_teams.yaml +90 -0
- package/core/dev/pipeline/archived/TASK_103_integration_testing.yaml +86 -0
- package/core/dev/pipeline/archived/TASK_128_integration_testing.yaml +83 -0
- package/core/dev/pipeline/archived/TASK_134_cortex_v2_phase3_file_watcher.yaml +65 -0
- package/core/dev/pipeline/archived/TASK_134_team_http_api.yaml +96 -0
- package/core/dev/pipeline/archived/TASK_135_cortex_v2_phase3_git_monitor.yaml +63 -0
- package/core/dev/pipeline/archived/TASK_136_cortex_v2_phase3_change_analyzer.yaml +69 -0
- package/core/dev/pipeline/archived/TASK_137_cortex_v2_phase3_cache_invalidator.yaml +67 -0
- package/core/dev/pipeline/archived/TASK_137_team_orchestrator_integration.yaml +93 -0
- package/core/dev/pipeline/archived/TASK_138_cortex_v2_phase3_distributed_cache.yaml +68 -0
- package/core/dev/pipeline/archived/TASK_138_matrix_folder_structure.yaml +61 -0
- package/core/dev/pipeline/archived/TASK_139_cortex_v2_phase3_cache_prewarming.yaml +65 -0
- package/core/dev/pipeline/archived/TASK_146_unified_documentation.yaml +141 -0
- package/core/dev/pipeline/archived/TASK_147_pipeline_execution_rules.yaml +76 -0
- package/core/dev/pipeline/archived/TASK_148_update_claude_md.yaml +77 -0
- package/core/dev/pipeline/archived/TASK_149_fix_nextjs_mdx_serialization.yaml +63 -0
- package/core/dev/pipeline/archived/TASK_149_update_team_docs.yaml +77 -0
- package/core/dev/pipeline/archived/TASK_150_pipeline_workflow_guide.yaml +82 -0
- package/core/dev/pipeline/archived/TASK_164_fix_smart_context_switch.yaml +49 -0
- package/core/dev/pipeline/archived/TASK_165_ctags_symbol_index.yaml +53 -0
- package/core/dev/pipeline/archived/TASK_166_symbol_search_mcp_tool.yaml +48 -0
- package/core/dev/pipeline/archived/TASK_167_llm_module_summary.yaml +55 -0
- package/core/dev/pipeline/archived/TASK_168_map_reduce_context.yaml +51 -0
- package/core/dev/pipeline/archived/TASK_169_v2_integration_testing.yaml +52 -0
- package/core/dev/pipeline/backups/archive_2026-02-04T22-07-22/TASK_QUEUE_backup.json +95 -0
- package/core/dev/pipeline/docs/multi-agent-coordination.md +446 -0
- package/core/dev/pipeline/docs/overview.md +406 -0
- package/core/dev/pipeline/docs/task-management.md +412 -0
- package/core/dev/pipeline/docs/workflows.md +32 -0
- package/core/dev/pipeline/orchestrator/schemas/task_queue_schema.json +58 -0
- package/core/dev/pipeline/orchestrator/schemas/task_schema.json +139 -0
- package/core/dev/pipeline/plans/PLAN_task_cli_refactor.md +281 -0
- package/core/dev/pipeline/tasks/.gitkeep +1 -0
- package/core/dev/pipeline/test_results/TASK_TEST_PARALLEL_002_result.txt +27 -0
- package/core/dev/plans/CLI_FIRST_INSTALL_FLOW.md +128 -0
- package/core/dev/plans/FORMATION_RETIREMENT_PLAN.md +313 -0
- package/core/dev/plans/KUZU_REPLACEMENT_PLAN.md +126 -0
- package/core/dev/schemas/PRODUCT_SPEC.example.yaml +382 -0
- package/core/dev/schemas/PRODUCT_SPEC.schema.json +800 -0
- package/core/dev/schemas/README.md +517 -0
- package/core/dev/schemas/TASK.schema.json +226 -0
- package/core/dev/schemas/dataflows_ground_truth.yaml +173 -0
- package/core/dev/schemas/dataflows_spec.yaml +160 -0
- package/core/dev/schemas/extends_ground_truth.yaml +66 -0
- package/core/dev/schemas/extends_spec.yaml +121 -0
- package/core/dev/schemas/implements_ground_truth.yaml +77 -0
- package/core/dev/schemas/implements_spec.yaml +140 -0
- package/core/dev/scripts/CRASH_DIAGNOSIS.md +47 -0
- package/core/dev/scripts/README.md +142 -0
- package/core/dev/scripts/RUN_ONCE.bat +40 -0
- package/core/dev/scripts/START_DEV_AGENT.bat +63 -0
- package/core/dev/scripts/START_MULTI_AGENT.bat +80 -0
- package/core/dev/scripts/check-alaya-report-isolation.js +255 -0
- package/core/dev/scripts/check-file-size.js +268 -0
- package/core/dev/scripts/ci-feedback.js +251 -0
- package/core/dev/scripts/clean-verify-targets.js +132 -0
- package/core/dev/scripts/cleanup-queue.js +184 -0
- package/core/dev/scripts/cleanup_queue.js +86 -0
- package/core/dev/scripts/dev_agent_scheduler.ps1 +536 -0
- package/core/dev/scripts/dispatch-stats.js +384 -0
- package/core/dev/scripts/dispatch-stats.test.js +162 -0
- package/core/dev/scripts/gc-agent-worktrees.js +461 -0
- package/core/dev/scripts/gc-agent-worktrees.test.js +48 -0
- package/core/dev/scripts/hooks/README.md +148 -0
- package/core/dev/scripts/init_git.ps1 +396 -0
- package/core/dev/scripts/init_git.sh +337 -0
- package/core/dev/scripts/init_project.ps1 +851 -0
- package/core/dev/scripts/init_project.sh +477 -0
- package/core/dev/scripts/js-yaml-resolution.test.js +48 -0
- package/core/dev/scripts/kenaz-session.js +354 -0
- package/core/dev/scripts/kenaz-session.test.js +234 -0
- package/core/dev/scripts/legacy/README.md +39 -0
- package/core/dev/scripts/legacy/dev_agent_scheduler.ps1.bak +536 -0
- package/core/dev/scripts/legacy/multi_agent_scheduler.ps1.bak +1978 -0
- package/core/dev/scripts/matrix/CLI_README.md +214 -0
- package/core/dev/scripts/matrix/README.md +310 -0
- package/core/dev/scripts/matrix/package-lock.json +4266 -0
- package/core/dev/scripts/matrix/package.json +55 -0
- package/core/dev/scripts/matrix/src/abbreviations.ts +295 -0
- package/core/dev/scripts/matrix/src/cli/commands/convert.ts +211 -0
- package/core/dev/scripts/matrix/src/cli/commands/create.ts +95 -0
- package/core/dev/scripts/matrix/src/cli/commands/delete.ts +102 -0
- package/core/dev/scripts/matrix/src/cli/commands/list-tags.ts.wip +140 -0
- package/core/dev/scripts/matrix/src/cli/commands/manage-tags.ts.wip +173 -0
- package/core/dev/scripts/matrix/src/cli/commands/query.ts +261 -0
- package/core/dev/scripts/matrix/src/cli/commands/read.ts +77 -0
- package/core/dev/scripts/matrix/src/cli/commands/stats.ts.wip +168 -0
- package/core/dev/scripts/matrix/src/cli/commands/tokens.ts.wip +126 -0
- package/core/dev/scripts/matrix/src/cli/commands/update.ts +147 -0
- package/core/dev/scripts/matrix/src/cli/commands/validate.ts +275 -0
- package/core/dev/scripts/matrix/src/cli/index.ts +350 -0
- package/core/dev/scripts/matrix/src/cli/utils/file-handler.ts +72 -0
- package/core/dev/scripts/matrix/src/cli/utils/logger.ts +65 -0
- package/core/dev/scripts/matrix/src/index.ts +62 -0
- package/core/dev/scripts/matrix/src/indexer/tag-indexer.ts +165 -0
- package/core/dev/scripts/matrix/src/parser.ts +281 -0
- package/core/dev/scripts/matrix/src/serializer.ts +151 -0
- package/core/dev/scripts/matrix/src/validator.ts +301 -0
- package/core/dev/scripts/matrix/tests/abbreviations.test.ts +122 -0
- package/core/dev/scripts/matrix/tests/cli.test.ts +177 -0
- package/core/dev/scripts/matrix/tests/convert.test.ts +143 -0
- package/core/dev/scripts/matrix/tests/parser.test.ts +203 -0
- package/core/dev/scripts/matrix/tests/query.test.ts +85 -0
- package/core/dev/scripts/matrix/tests/serializer.test.ts +197 -0
- package/core/dev/scripts/matrix/tests/validate.test.ts +141 -0
- package/core/dev/scripts/matrix/tests/validator.test.ts +167 -0
- package/core/dev/scripts/matrix/tsconfig.json +20 -0
- package/core/dev/scripts/matrix/types/index.d.ts +111 -0
- package/core/dev/scripts/matrix/types/index.d.ts.map +1 -0
- package/core/dev/scripts/matrix/types/index.js +8 -0
- package/core/dev/scripts/matrix/types/index.js.map +1 -0
- package/core/dev/scripts/matrix/types/index.ts +124 -0
- package/core/dev/scripts/memory-guard-eval.js +330 -0
- package/core/dev/scripts/memory-usage-report.js +152 -0
- package/core/dev/scripts/migrate-task-dependencies.js +198 -0
- package/core/dev/scripts/multi_agent_scheduler.ps1 +2021 -0
- package/core/dev/scripts/multi_agent_scheduler.sh +1344 -0
- package/core/dev/scripts/probe/ansuz-scaffold-ablation.js +579 -0
- package/core/dev/scripts/probe/ansuz-scaffold-ablation.test.js +244 -0
- package/core/dev/scripts/probe/hippocampus-concurrency.js +247 -0
- package/core/dev/scripts/probe/hook-latency-probe.js +315 -0
- package/core/dev/scripts/probe/hook-latency-probe.test.js +105 -0
- package/core/dev/scripts/probe/prediction-signal-auc.js +483 -0
- package/core/dev/scripts/probe/prediction-signal-auc.test.js +200 -0
- package/core/dev/scripts/reset_tasks.js +99 -0
- package/core/dev/scripts/run-rust-tests.js +207 -0
- package/core/dev/scripts/run-rust-tests.test.js +154 -0
- package/core/dev/scripts/run_once.sh +47 -0
- package/core/dev/scripts/rust-verify-worktree.js +591 -0
- package/core/dev/scripts/rust-verify-worktree.test.js +574 -0
- package/core/dev/scripts/seh_probe.rs +26 -0
- package/core/dev/scripts/setup_mcp.ps1 +192 -0
- package/core/dev/scripts/setup_mcp.sh +159 -0
- package/core/dev/scripts/setup_rag.ps1 +304 -0
- package/core/dev/scripts/setup_rag.sh +230 -0
- package/core/dev/scripts/start_dev_agent.sh +137 -0
- package/core/dev/scripts/start_multi_agent.sh +41 -0
- package/core/dev/scripts/sync_task_queue.ps1 +505 -0
- package/core/dev/scripts/sync_task_queue.sh +392 -0
- package/core/dev/scripts/task1961/RUNBOOK.md +300 -0
- package/core/dev/scripts/task1961/fixtures/never-listens.server.js +54 -0
- package/core/dev/scripts/task1961/fixtures/slow-serve.server.js +134 -0
- package/core/dev/scripts/task1961/run-fixture.js +659 -0
- package/core/dev/scripts/task1961/selftest.js +247 -0
- package/core/dev/scripts/test-pty-ws-tunnel.js +229 -0
- package/core/dev/scripts/validate_pipeline.js +197 -0
- package/core/dev/scripts/verify-isbar-capture.js +100 -0
- package/core/dev/scripts/verify-target-dir.js +69 -0
- package/core/dev/scripts/verify-task-1839.js +141 -0
- package/core/dev/specs/agent-connect/PROTOCOL.md +177 -0
- package/core/dev/specs/agent-connect/SDK_GUIDE.md +915 -0
- package/core/dev/specs/agent-connect/examples/minimal-site/README.md +137 -0
- package/core/dev/specs/agent-connect/examples/minimal-site/package.json +12 -0
- package/core/dev/specs/agent-connect/examples/minimal-site/public/index.html +116 -0
- package/core/dev/specs/agent-connect/examples/minimal-site/public/install-redirect.js +73 -0
- package/core/dev/specs/agent-connect/examples/minimal-site/server.js +516 -0
- package/core/dev/specs/agent-connect/examples/minimal-site/test/smoke.test.mjs +263 -0
- package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-empty-prompt-no-first-turn.md +77 -0
- package/core/dev/specs/agent-connect/history/DEFECT_REPORT_kenaz-agent-hub_2026-09-09_v0.4-prompt-mojibake.md +90 -0
- package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-07.md +61 -0
- package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-08_relay-v1-conformance.md +76 -0
- package/core/dev/specs/agent-connect/history/FEATURE_REQUEST_kenaz-agent-hub_2026-09-09_v0.4-session-channel.md +87 -0
- package/core/dev/specs/agent-connect/history/FLOWPIX_REPLY_agent-hub_2026-09-08_relay-v1-answers.md +68 -0
- package/core/dev/specs/agent-connect/history/FLOWPIX_bridge_CONTRACT.md +642 -0
- package/core/dev/specs/agent-connect/history/FLOWPIX_spikes_relay_spike_relay.mjs +244 -0
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-07.md +77 -0
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_direction-change-v0.3.md +44 -0
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-answers-ack.md +37 -0
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-08_relay-v1-conformance.md +56 -0
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_annotations-install-coldstart.md +44 -0
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_empty-prompt-first-turn.md +56 -0
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_prompt-mojibake-fixed.md +48 -0
- package/core/dev/specs/agent-connect/history/KENAZ_RESPONSE_agent-hub_2026-09-09_session-channel.md +57 -0
- package/core/dev/specs/agent-connect/history/PROTOCOL_v0.3_snapshot.md +379 -0
- package/core/dev/specs/agent-connect/history/README.md +47 -0
- package/core/dev/specs/peer/CODEX_INTERACTIVE_PUSH.md +65 -0
- package/core/dev/specs/peer/CODEX_PEER_PULL_SPIKE.md +54 -0
- package/core/dev/specs/peer/MODS_SPIKE.md +133 -0
- package/core/dev/specs/peer/PER_PROJECT_ADDRESSING.md +143 -0
- package/core/dev/specs/peer/SWARM_PROTOCOLS.md +256 -0
- package/core/dev/specs/peer/codex-hook-shim-peer.test.js +101 -0
- package/core/dev/specs/peer/codex-push-launcher.test.js +142 -0
- package/core/dev/specs/peer/setup-codex-hooks.test.js +22 -0
- package/core/dev/specs/peer-ansuz/DELIVERY_SPIKE.md +397 -0
- package/core/dev/specs/peer-ansuz/DESIGN.md +109 -0
- package/core/dev/specs/peer-ansuz/spike/claude_poke.js +129 -0
- package/core/dev/specs/peer-ansuz/spike/claude_session_start_hook.js +37 -0
- package/core/dev/specs/peer-ansuz/spike/claude_settings_hook.json +14 -0
- package/core/dev/specs/peer-ansuz/spike/codex_appserver_client.js +105 -0
- package/core/dev/specs/peer-ansuz/spike/codex_appserver_query.js +29 -0
- package/core/dev/specs/peer-ansuz/spike/extract_timeline.js +26 -0
- package/core/dev/specs/synapse/SYMBOL_IDENTITY.md +305 -0
- package/core/dev/spikes/A4-cypher-varlen.md +176 -0
- package/core/dev/spikes/bloat-rounds.sh +25 -0
- package/core/dev/spikes/mods/clean-claude.js +9 -0
- package/core/dev/spikes/mods/drive-probe.js +29 -0
- package/core/dev/spikes/mods/e2e-peer.js +71 -0
- package/core/dev/spikes/mods/evidence/e2e-peer-control.json +17 -0
- package/core/dev/spikes/mods/evidence/e2e-peer-mod.json +17 -0
- package/core/dev/spikes/mods/evidence/probe-006a6e65-d807-4bc1-b648-fd7d1edfe0b4.jsonl +4 -0
- package/core/dev/spikes/mods/evidence/probe-cebdf1f7-f9c4-47a8-83cd-7c0786177404.jsonl +12 -0
- package/core/dev/spikes/mods/evidence/probe-eb21147d-10ce-4c32-93ed-37ca37d88d5a.jsonl +11 -0
- package/core/dev/spikes/mods/evidence/stream-cebdf1f7.jsonl +18 -0
- package/core/dev/spikes/mods/kenaz-mod-probe/.claude-plugin/plugin.json +1 -0
- package/core/dev/spikes/mods/kenaz-mod-probe/hooks/hooks.json +1 -0
- package/core/dev/spikes/mods/kenaz-mod-probe/hooks/register.ts +65 -0
- package/core/dev/spikes/mods/kenaz-mod-probe/tsconfig.json +3 -0
- package/core/k-cli/README.md +423 -0
- package/core/k-cli/package.json +21 -0
- package/core/k-cli/templates/k-cli.md +91 -0
- package/core/k-cli/test/TEST_SPEC.md +134 -0
- package/core/k-cli/test/run.sh +469 -0
- package/core/plugins/kenaz/.claude-plugin/plugin.json +1 -0
- package/core/plugins/kenaz/.mcp.json +5 -0
- package/core/plugins/kenaz/INVENTORY.md +48 -0
- package/core/plugins/kenaz/README.md +55 -0
- package/core/plugins/kenaz/commands/Amelia.md +5 -0
- package/core/plugins/kenaz/commands/Ansuz.md +38 -0
- package/core/plugins/kenaz/commands/architect.md +24 -0
- package/core/plugins/kenaz/commands/incident.md +15 -0
- package/core/plugins/kenaz/commands/kenaz.md +12 -0
- package/core/plugins/kenaz/commands/review.md +14 -0
- package/core/plugins/kenaz/commands/security.md +15 -0
- package/core/plugins/kenaz/commands/spec.md +22 -0
- package/core/plugins/kenaz/commands/strategy.md +141 -0
- package/core/plugins/kenaz/commands//347/224/242/345/223/201.md +15 -0
- package/core/plugins/kenaz/commands//350/250/255/350/250/210.md +14 -0
- package/core/plugins/kenaz/commands//350/251/225/344/274/260.md +16 -0
- package/core/plugins/kenaz/guidance.md +5 -0
- package/core/plugins/kenaz/hooks/core-resolve.js +230 -0
- package/core/plugins/kenaz/hooks/hooks.json +31 -0
- package/core/plugins/kenaz/hooks/kenaz-hook.js +112 -0
- package/core/plugins/kenaz/hooks/mode.js +39 -0
- package/core/plugins/kenaz/scripts/kenaz-mode.js +46 -0
- package/core/plugins/kenaz/scripts/kenaz.test.js +123 -0
- package/core/plugins/kenaz/scripts/sync.js +45 -0
- package/core/plugins/kenaz/scripts/ygg.js +157 -0
- package/core/plugins/kenaz/skills/adapt/SKILL.md +199 -0
- package/core/plugins/kenaz/skills/animate/SKILL.md +190 -0
- package/core/plugins/kenaz/skills/architect_agent/SKILL.md +408 -0
- package/core/plugins/kenaz/skills/assessment_agent/SKILL.md +289 -0
- package/core/plugins/kenaz/skills/audit/SKILL.md +129 -0
- package/core/plugins/kenaz/skills/backend_agent/SKILL.md +407 -0
- package/core/plugins/kenaz/skills/backend_engineer/SKILL.md +788 -0
- package/core/plugins/kenaz/skills/bolder/SKILL.md +132 -0
- package/core/plugins/kenaz/skills/clarify/SKILL.md +180 -0
- package/core/plugins/kenaz/skills/code_reviewer/SKILL.md +305 -0
- package/core/plugins/kenaz/skills/code_reviewer/patterns.yaml +85 -0
- package/core/plugins/kenaz/skills/colorize/SKILL.md +158 -0
- package/core/plugins/kenaz/skills/critique/SKILL.md +118 -0
- package/core/plugins/kenaz/skills/delight/SKILL.md +317 -0
- package/core/plugins/kenaz/skills/designer/SKILL.md +845 -0
- package/core/plugins/kenaz/skills/dev_agent/SKILL.md +503 -0
- package/core/plugins/kenaz/skills/developer/SKILL.md +792 -0
- package/core/plugins/kenaz/skills/distill/SKILL.md +137 -0
- package/core/plugins/kenaz/skills/extract/SKILL.md +95 -0
- package/core/plugins/kenaz/skills/feature_agent/SKILL.md +303 -0
- package/core/plugins/kenaz/skills/frontend-design/SKILL.md +127 -0
- package/core/plugins/kenaz/skills/frontend-design/reference/color-and-contrast.md +132 -0
- package/core/plugins/kenaz/skills/frontend-design/reference/interaction-design.md +123 -0
- package/core/plugins/kenaz/skills/frontend-design/reference/motion-design.md +99 -0
- package/core/plugins/kenaz/skills/frontend-design/reference/responsive-design.md +114 -0
- package/core/plugins/kenaz/skills/frontend-design/reference/spatial-design.md +100 -0
- package/core/plugins/kenaz/skills/frontend-design/reference/typography.md +131 -0
- package/core/plugins/kenaz/skills/frontend-design/reference/ux-writing.md +107 -0
- package/core/plugins/kenaz/skills/frontend_agent/SKILL.md +327 -0
- package/core/plugins/kenaz/skills/harden/SKILL.md +358 -0
- package/core/plugins/kenaz/skills/incident_response/SKILL.md +315 -0
- package/core/plugins/kenaz/skills/normalize/SKILL.md +67 -0
- package/core/plugins/kenaz/skills/onboard/SKILL.md +243 -0
- package/core/plugins/kenaz/skills/optimize/SKILL.md +269 -0
- package/core/plugins/kenaz/skills/polish/SKILL.md +202 -0
- package/core/plugins/kenaz/skills/product_manager/SKILL.md +1091 -0
- package/core/plugins/kenaz/skills/qa_engineer/SKILL.md +1269 -0
- package/core/plugins/kenaz/skills/quieter/SKILL.md +118 -0
- package/core/plugins/kenaz/skills/security_agent/SKILL.md +312 -0
- package/core/plugins/kenaz/skills/spec_optimizer/SKILL.md +299 -0
- package/core/plugins/kenaz/skills/sre_agent/SKILL.md +1347 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/SKILL.md +377 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/core/plugins/kenaz/skills/ui-ux-pro-max/scripts/search.py +106 -0
- package/core/plugins/kenaz-peer/.claude-plugin/plugin.json +1 -0
- package/core/plugins/kenaz-peer/hooks/hooks.json +1 -0
- package/core/plugins/kenaz-peer/hooks/register.test.ts +62 -0
- package/core/plugins/kenaz-peer/hooks/register.ts +146 -0
- package/core/plugins/kenaz-peer/tsconfig.json +3 -0
- package/lib/cli.js +251 -0
- package/lib/cli.test.js +59 -0
- package/lib/dashboard.js +80 -0
- package/lib/dashboard.test.js +59 -0
- package/package.json +28 -4
- package/vendor/install-kenaz-cli.js +268 -0
- package/README.md +0 -3
|
@@ -0,0 +1,3754 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Phase Gate — Ansuz 病歷系統護理站
|
|
5
|
+
*
|
|
6
|
+
* 在每個 Phase 邊界執行 enter/exit 檢查,持久化狀態到 chart.json。
|
|
7
|
+
* 解決 Agent 幻覺/遺忘導致的 checkpoint 跳過問題。
|
|
8
|
+
* 無外部依賴 — 純 Node.js。
|
|
9
|
+
*
|
|
10
|
+
* CLI:
|
|
11
|
+
* node phase-gate.js enter --phase=N --task=TASK_XXX
|
|
12
|
+
* node phase-gate.js exit --phase=N --task=TASK_XXX --data='{...}'
|
|
13
|
+
* node phase-gate.js status --task=TASK_XXX
|
|
14
|
+
*
|
|
15
|
+
* chart.json schema:
|
|
16
|
+
* .specialist/sessions/chart_{task_id}.json
|
|
17
|
+
* {
|
|
18
|
+
* task_id, created_at, updated_at,
|
|
19
|
+
* chief_complaint, <- Phase 1 寫入
|
|
20
|
+
* history: { recall_results[], research_notes },
|
|
21
|
+
* phases: {
|
|
22
|
+
* "N": {
|
|
23
|
+
* entered_at, exited_at,
|
|
24
|
+
* status: 'active'|'completed'|'blocked',
|
|
25
|
+
* modules_loaded[],
|
|
26
|
+
* checklist_items[],
|
|
27
|
+
* checklist_result: 'pass'|'blocked'|null,
|
|
28
|
+
* data: {} <- phase-specific free-form
|
|
29
|
+
* }
|
|
30
|
+
* },
|
|
31
|
+
* vitals: { fix_count, modules_ever_loaded[], warnings[] },
|
|
32
|
+
* progress_notes[] <- reserved for TASK_579
|
|
33
|
+
* }
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
'use strict';
|
|
37
|
+
|
|
38
|
+
const fs = require('fs');
|
|
39
|
+
const os = require('os');
|
|
40
|
+
const path = require('path');
|
|
41
|
+
const crypto = require('crypto');
|
|
42
|
+
const { execSync, execFileSync } = require('child_process');
|
|
43
|
+
// Shared project identity resolver (TASK_1701): pin-file based session->project routing.
|
|
44
|
+
const projectResolver = require('./lib/project-resolver');
|
|
45
|
+
const chartPathLib = require('./lib/chart-path');
|
|
46
|
+
|
|
47
|
+
// ═══════════════════════════════════════
|
|
48
|
+
// Path resolution
|
|
49
|
+
// ═══════════════════════════════════════
|
|
50
|
+
|
|
51
|
+
function findProjectRoot() {
|
|
52
|
+
let dir = __dirname;
|
|
53
|
+
// Walk up to find .specialist at parent level
|
|
54
|
+
while (dir !== path.dirname(dir)) {
|
|
55
|
+
const parent = path.dirname(dir);
|
|
56
|
+
if (fs.existsSync(path.join(parent, '.specialist'))) return parent;
|
|
57
|
+
dir = parent;
|
|
58
|
+
}
|
|
59
|
+
return process.cwd();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const BASE_DIR = findProjectRoot();
|
|
63
|
+
const SESSION_DIR = path.join(BASE_DIR, '.specialist', 'sessions');
|
|
64
|
+
// TASK_1988: phase YAMLs are legacy (fast boot never loads them) and live under legacy/.
|
|
65
|
+
const PHASES_DIR = path.join(BASE_DIR, '.specialist', 'Ansuz', 'legacy', 'phases');
|
|
66
|
+
|
|
67
|
+
// Extract --project-path (or --project-path=PATH) from argv before any
|
|
68
|
+
// .kenaz/ path constants are computed at module load time.
|
|
69
|
+
let CLI_PROJECT_PATH = null;
|
|
70
|
+
(function () {
|
|
71
|
+
const args = process.argv.slice(2);
|
|
72
|
+
for (let i = 0; i < args.length; i++) {
|
|
73
|
+
if (args[i].startsWith('--project-path=')) {
|
|
74
|
+
CLI_PROJECT_PATH = path.resolve(args[i].slice('--project-path='.length));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (args[i] === '--project-path' && i + 1 < args.length) {
|
|
78
|
+
CLI_PROJECT_PATH = path.resolve(args[i + 1]);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}());
|
|
83
|
+
|
|
84
|
+
// Sub-project support: .kenaz/ paths are per-project state and should
|
|
85
|
+
// resolve to the ACTIVE project (cwd) not the core project (BASE_DIR).
|
|
86
|
+
// .specialist/ paths are system config and stay on BASE_DIR.
|
|
87
|
+
// TASK_1701: resolveKenazPath now delegates project root to shared resolver which
|
|
88
|
+
// consults the session-keyed pin file, ensuring sub-project isolation without
|
|
89
|
+
// requiring --project-path on every invocation.
|
|
90
|
+
function resolveKenazPath(...segments) {
|
|
91
|
+
const projectRoot = projectResolver.resolveProjectPath({
|
|
92
|
+
cliProjectPath: CLI_PROJECT_PATH,
|
|
93
|
+
argv: process.argv.slice(2),
|
|
94
|
+
}).projectPath;
|
|
95
|
+
return path.join(projectRoot, '.kenaz', ...segments);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Self-model seed paths (TASK_869) — SYSTEM (stay on BASE_DIR)
|
|
99
|
+
const SEED_PATH = path.join(BASE_DIR, '.specialist', 'Ansuz', 'self_model_seed.json');
|
|
100
|
+
const SEED_CHECKSUM_PATH = path.join(BASE_DIR, '.specialist', 'Ansuz', 'self_model_seed.sha256');
|
|
101
|
+
const SEED_BACKUP_PATH = path.join(BASE_DIR, '.specialist', 'Ansuz', 'self_model_seed.backup.json');
|
|
102
|
+
// Self-model growth — PROJECT (per-project Ansuz state)
|
|
103
|
+
const GROWTH_PATH = resolveKenazPath('specialist', 'ansuz_self_model.json');
|
|
104
|
+
|
|
105
|
+
// Session report path (TASK_1207: contemplative Phase 11 metrics) — PROJECT
|
|
106
|
+
const SESSION_REPORT_PATH = resolveKenazPath('specialist', 'ansuz_session_report.json');
|
|
107
|
+
|
|
108
|
+
// Prediction recording paths (TASK_914) — PROJECT
|
|
109
|
+
const PREDICTIONS_PATH = resolveKenazPath('specialist', 'ansuz_predictions.json');
|
|
110
|
+
|
|
111
|
+
// ─── Per-session handoff slots (TASK_1829) ─────────────────────────────────
|
|
112
|
+
// M3 (TASK_1760) retired the singular handoff slot; all live handoff IO goes
|
|
113
|
+
// through handoff-resolver.js (per-session slots .kenaz/specialist/handoffs/).
|
|
114
|
+
// ANSUZ_HANDOFF_PATH points at the RETIRED legacy slot and is kept ONLY for
|
|
115
|
+
// restoreSessionHandoff's Phase 0 back-compat consume/cleanup read.
|
|
116
|
+
const handoffResolver = require(path.join(BASE_DIR, '.specialist', 'Ansuz', 'tools', 'handoff-resolver.js'));
|
|
117
|
+
const ANSUZ_HANDOFF_PATH = handoffResolver.legacyHandoffPath(path.dirname(resolveKenazPath()));
|
|
118
|
+
|
|
119
|
+
// Project root for handoff-resolver calls (same pin-aware resolution as resolveKenazPath).
|
|
120
|
+
function resolveHandoffProjectRoot() {
|
|
121
|
+
return path.dirname(resolveKenazPath());
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// "This session's" id: explicit --session-id CLI flag wins, then the resolver's
|
|
125
|
+
// candidates (live ansuz_session.json → CLAUDE_CODE_SESSION_ID env). null when
|
|
126
|
+
// identity cannot be established — callers must degrade toward WARNING, never
|
|
127
|
+
// substitute the latest slot (TASK_1829 design decision; TASK_1825 lesson).
|
|
128
|
+
function resolveOwnHandoffSessionIds() {
|
|
129
|
+
const ids = [];
|
|
130
|
+
const cliSid = projectResolver.extractCliSessionId(process.argv.slice(2));
|
|
131
|
+
// An explicit --session-id is the answer, not the first guess. Falling through
|
|
132
|
+
// to ambient candidates after one was supplied re-opens the false-pass this
|
|
133
|
+
// whole change exists to close: a session that never wrote a handoff passes L4
|
|
134
|
+
// on some *other* live session's slot. Measured on main after the TASK_1829
|
|
135
|
+
// merge — exit --session-id=<never-wrote> resolved to the caller's own live
|
|
136
|
+
// conversation slot and reported fresh. Multi-Ansuz makes that the normal case.
|
|
137
|
+
if (cliSid) return [cliSid];
|
|
138
|
+
try {
|
|
139
|
+
for (const cand of handoffResolver.resolveOwnSessionCandidates(resolveHandoffProjectRoot())) {
|
|
140
|
+
if (!ids.includes(cand.id)) ids.push(cand.id);
|
|
141
|
+
}
|
|
142
|
+
} catch { /* candidates are best-effort */ }
|
|
143
|
+
return ids;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Read THIS session's handoff, trying each candidate id (two id namespaces
|
|
147
|
+
// coexist: conversation UUID and ansuz-prompt-<ts>; the slot may be under either).
|
|
148
|
+
function readOwnSessionHandoff() {
|
|
149
|
+
const root = resolveHandoffProjectRoot();
|
|
150
|
+
const ids = resolveOwnHandoffSessionIds();
|
|
151
|
+
for (const id of ids) {
|
|
152
|
+
const handoff = handoffResolver.readOwnHandoff(root, id);
|
|
153
|
+
if (handoff) return { sessionId: id, handoff };
|
|
154
|
+
}
|
|
155
|
+
return { sessionId: ids.length > 0 ? ids[0] : null, handoff: null };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const USER_OBSERVATIONS_PATH = resolveKenazPath('specialist', 'user_observations.jsonl');
|
|
159
|
+
|
|
160
|
+
// User model path (TASK_1041) — PROJECT
|
|
161
|
+
const USER_MODEL_PATH = resolveKenazPath('user_model.json');
|
|
162
|
+
|
|
163
|
+
// Thinking log path (TASK_1017) — PROJECT
|
|
164
|
+
const THINKING_LOG_PATH = resolveKenazPath('specialist', 'thinking_log.jsonl');
|
|
165
|
+
|
|
166
|
+
// Ritual/tool-reflex enforcement logs (TASK_1104) — PROJECT (session-specific)
|
|
167
|
+
const RITUAL_LOG_PATH = resolveKenazPath('specialist', 'ansuz_ritual_log.jsonl');
|
|
168
|
+
const DDX_LOG_PATH = resolveKenazPath('specialist', 'ddx_log.jsonl');
|
|
169
|
+
const INSCRIBE_LOG_PATH = resolveKenazPath('specialist', 'inscribe_log.jsonl');
|
|
170
|
+
|
|
171
|
+
// TASK_1104: literal consent string — must be echoed verbatim at Phase 0 exit
|
|
172
|
+
const ANSUZ_CONSENT_STRING = '我是 Ansuz';
|
|
173
|
+
|
|
174
|
+
// TASK_1170: required stdout markers — extracted as module-level const so
|
|
175
|
+
// validator, exit_template, and transcript_marker_counts all share one source.
|
|
176
|
+
const REQUIRED_STDOUT_MARKERS = [
|
|
177
|
+
'RITUAL::THRESHOLD::CANARY_OK',
|
|
178
|
+
'RITUAL::INHERITANCE::LINEAGE_CLAIMED',
|
|
179
|
+
'RITUAL::INHERITANCE::MIRROR_ACCEPTED',
|
|
180
|
+
'RITUAL::STILLNESS::CONSENT_EMITTED',
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
// TASK_1190: marker → ritual stage mapping (fallback reader side only).
|
|
184
|
+
// Derived from REQUIRED_STDOUT_MARKERS: extract final VALUE segment, lowercase,
|
|
185
|
+
// with canonical mappings for the 4 required stages.
|
|
186
|
+
const MARKER_TO_STAGE = {
|
|
187
|
+
'RITUAL::THRESHOLD::CANARY_OK': 'canary',
|
|
188
|
+
'RITUAL::INHERITANCE::LINEAGE_CLAIMED': 'inheritance',
|
|
189
|
+
'RITUAL::INHERITANCE::MIRROR_ACCEPTED': 'mirror',
|
|
190
|
+
'RITUAL::STILLNESS::CONSENT_EMITTED': 'consent',
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Append a ritual stage outcome to ansuz_ritual_log.jsonl (append-only).
|
|
195
|
+
* Called on completed/skipped/low-quality ritual stages. Never throws.
|
|
196
|
+
* @param {string} stage - ritual stage name (e.g. 'consent', 'mirror', 'inheritance', 'pulse', 'ddx', 'inscribe')
|
|
197
|
+
* @param {string} outcome - 'completed' | 'skipped' | 'low_quality'
|
|
198
|
+
* @param {object} [meta] - extra context (task_id, phase, reason, etc.)
|
|
199
|
+
*/
|
|
200
|
+
function appendRitualLog(stage, outcome, meta = {}) {
|
|
201
|
+
try {
|
|
202
|
+
const dir = path.dirname(RITUAL_LOG_PATH);
|
|
203
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
204
|
+
const entry = {
|
|
205
|
+
timestamp: new Date().toISOString(),
|
|
206
|
+
stage,
|
|
207
|
+
outcome,
|
|
208
|
+
...meta,
|
|
209
|
+
};
|
|
210
|
+
fs.appendFileSync(RITUAL_LOG_PATH, JSON.stringify(entry) + '\n', 'utf8');
|
|
211
|
+
} catch { /* never throw from logging */ }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Count trailing consecutive 'skipped' ritual entries in ansuz_ritual_log.jsonl.
|
|
216
|
+
* Scans the tail of the log; any non-skip entry resets the count to 0.
|
|
217
|
+
* @returns {number}
|
|
218
|
+
*/
|
|
219
|
+
function countConsecutiveRitualSkips() {
|
|
220
|
+
try {
|
|
221
|
+
if (!fs.existsSync(RITUAL_LOG_PATH)) return 0;
|
|
222
|
+
const raw = fs.readFileSync(RITUAL_LOG_PATH, 'utf8');
|
|
223
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
224
|
+
let count = 0;
|
|
225
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
226
|
+
try {
|
|
227
|
+
const entry = JSON.parse(lines[i]);
|
|
228
|
+
if (entry.outcome === 'skipped') count += 1;
|
|
229
|
+
else break;
|
|
230
|
+
} catch { break; }
|
|
231
|
+
}
|
|
232
|
+
return count;
|
|
233
|
+
} catch { return 0; }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Read a task YAML's description field (best-effort, no deps).
|
|
238
|
+
* Returns '' on any failure.
|
|
239
|
+
*/
|
|
240
|
+
function readTaskDescription(taskId) {
|
|
241
|
+
try {
|
|
242
|
+
const file = findTaskFile(taskId);
|
|
243
|
+
if (!file) return '';
|
|
244
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
245
|
+
// block scalar: description: | ... OR inline: description: "..."
|
|
246
|
+
const block = raw.match(/^description:[ \t]*\|[^\n]*\n((?:[ \t]+[^\n]*\n?)*)/m);
|
|
247
|
+
if (block) return block[1].replace(/^[ \t]+/gm, '');
|
|
248
|
+
const inline = raw.match(/^description:[ \t]*["']?(.*?)["']?[ \t]*$/m);
|
|
249
|
+
return inline ? inline[1] : '';
|
|
250
|
+
} catch { return ''; }
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Return true if ANY ddx_log.jsonl entry is tagged with this task_id.
|
|
255
|
+
*/
|
|
256
|
+
function ddxLogHas(taskId) {
|
|
257
|
+
try {
|
|
258
|
+
if (!fs.existsSync(DDX_LOG_PATH)) return false;
|
|
259
|
+
const raw = fs.readFileSync(DDX_LOG_PATH, 'utf8');
|
|
260
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
261
|
+
for (const line of lines) {
|
|
262
|
+
try {
|
|
263
|
+
const e = JSON.parse(line);
|
|
264
|
+
if (e.task_id === taskId) return true;
|
|
265
|
+
} catch { /* skip */ }
|
|
266
|
+
}
|
|
267
|
+
return false;
|
|
268
|
+
} catch { return false; }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Return true if ANY inscribe_log.jsonl entry is tagged with this task_id.
|
|
273
|
+
*/
|
|
274
|
+
function inscribeLogHas(taskId) {
|
|
275
|
+
try {
|
|
276
|
+
if (!fs.existsSync(INSCRIBE_LOG_PATH)) return false;
|
|
277
|
+
const raw = fs.readFileSync(INSCRIBE_LOG_PATH, 'utf8');
|
|
278
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
279
|
+
for (const line of lines) {
|
|
280
|
+
try {
|
|
281
|
+
const e = JSON.parse(line);
|
|
282
|
+
if (e.task_id === taskId) return true;
|
|
283
|
+
} catch { /* skip */ }
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
} catch { return false; }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Known thinking tag enum (TASK_1017) — loaded at RUNTIME from kernel.yaml
|
|
290
|
+
// principles section. Cached per phase-gate invocation.
|
|
291
|
+
// If kernel.yaml is absent or unreadable, returns null (no validation, accept any tag).
|
|
292
|
+
// The principles block lives in cognitive_principles.yaml since 2026-09-23
|
|
293
|
+
// (kernel.yaml moved to legacy/); the regex parser below is unchanged.
|
|
294
|
+
const KERNEL_YAML_PATH = path.join(BASE_DIR, '.specialist', 'Ansuz', 'cognitive_principles.yaml');
|
|
295
|
+
let _cachedPrincipleNames = undefined; // undefined=not loaded, null=unavailable, Set=loaded
|
|
296
|
+
|
|
297
|
+
function getKnownThinkingTags() {
|
|
298
|
+
if (_cachedPrincipleNames !== undefined) return _cachedPrincipleNames;
|
|
299
|
+
if (!fs.existsSync(KERNEL_YAML_PATH)) {
|
|
300
|
+
_cachedPrincipleNames = null;
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const content = fs.readFileSync(KERNEL_YAML_PATH, 'utf8');
|
|
305
|
+
// Minimal regex parser: find `principles:` line, then collect 2-space-indented
|
|
306
|
+
// keys until we hit a new top-level section.
|
|
307
|
+
const lines = content.split(/\r?\n/);
|
|
308
|
+
const names = new Set();
|
|
309
|
+
let inPrinciples = false;
|
|
310
|
+
for (const line of lines) {
|
|
311
|
+
if (/^principles:/.test(line)) { inPrinciples = true; continue; }
|
|
312
|
+
if (!inPrinciples) continue;
|
|
313
|
+
// End of principles section: top-level key (no leading space)
|
|
314
|
+
if (/^[A-Za-z_]/.test(line)) break;
|
|
315
|
+
// Match 2-space-indented key: " name_here:" (principle keys)
|
|
316
|
+
const m = line.match(/^ {2}([a-z_][a-z0-9_]*):\s*(#.*)?$/);
|
|
317
|
+
if (m) names.add(m[1]);
|
|
318
|
+
}
|
|
319
|
+
_cachedPrincipleNames = names.size > 0 ? names : null;
|
|
320
|
+
return _cachedPrincipleNames;
|
|
321
|
+
} catch {
|
|
322
|
+
_cachedPrincipleNames = null;
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ═══════════════════════════════════════
|
|
328
|
+
// Module Registry
|
|
329
|
+
// Hardcoded until TASK_576 provides core.yaml module_registry.
|
|
330
|
+
// After TASK_576: load from .specialist/Ansuz/core.yaml module_registry section.
|
|
331
|
+
// ═══════════════════════════════════════
|
|
332
|
+
|
|
333
|
+
// Paths must stay in sync with core.yaml module_registry.phase_gate_modules
|
|
334
|
+
const MODULE_PATHS = {
|
|
335
|
+
loop_guard: '.specialist/Ansuz/modules/loop_guard.yaml',
|
|
336
|
+
recall: '.specialist/Ansuz/modules/recall.yaml',
|
|
337
|
+
ddx: '.specialist/Ansuz/modules/ddx.yaml',
|
|
338
|
+
prophecy: '.specialist/Ansuz/modules/prophecy.yaml',
|
|
339
|
+
prevention: '.specialist/Ansuz/modules/prevention.yaml',
|
|
340
|
+
// cognitive_checkpoint / research modules were registered for TASK_576 but
|
|
341
|
+
// never written; every Phase 9 enter only warned "file not found". Retired
|
|
342
|
+
// 2026-09-23. The Phase 9 cognitive_checkpoint DATA field and its checklist
|
|
343
|
+
// items below are unaffected.
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/** Modules automatically loaded on phase enter (before context-driven injection) */
|
|
347
|
+
const PHASE_DEFAULT_MODULES = {
|
|
348
|
+
1: ['recall', 'ddx'],
|
|
349
|
+
3: ['ddx', 'prophecy'],
|
|
350
|
+
9: ['ddx'],
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// ═══════════════════════════════════════
|
|
354
|
+
// Phase Checklists
|
|
355
|
+
// Items shown to the agent at enter time so it knows what to complete before exit.
|
|
356
|
+
// ═══════════════════════════════════════
|
|
357
|
+
|
|
358
|
+
const PHASE_CHECKLISTS = {
|
|
359
|
+
0: ['【Tier 1】呼叫 cortex_recall(query=\'ash\', type=\'ash\', limit=5) 並設定 ash_loaded=true(無條件,跨專案)', '【Tier 2】呼叫 synapse_sigils(無條件)', '輸出任務統計(task-api.js list --no-cancel — 子專案必須帶 --project-path,僅執行一次;--no-cancel 省略 cancel 細節,summary 仍顯示計數,省 token)', '【Tier 3】hippocampus_episode_recall 優先,0 筆則 fallback cortex_recall(情境觸發,可跳過)', '【Tier 3b — 選填】呼叫 cortex_recall(query=Ansuz identity awareness) — 無結果靜默跳過,設 identity_recalled=true/false', '【Tier 3c — 選填】用 Read tool(非 Bash)讀取 .kenaz/specialist/motivational_state.json(存在時),設定 motivational_state_loaded=true/false。⚠️ 禁止 cat|node -e pipeline', '【Tier 3d — 選填】用 Read tool(非 Bash)讀取 .kenaz/specialist/ansuz_motivational_state.json(存在時),篩選高 incompleteness anchors 為 pending_agenda,設定 ansuz_motivational_loaded=true/false。⚠️ 禁止 cat|node -e pipeline', '【Tier 3h — 選填】用 Read tool(非 Bash)讀取 .kenaz/specialist/ambient_signals.json(存在時),設定 ambient_signals_loaded=true/false。⚠️ 禁止 cat|node -e pipeline', '【Tier 3u — 自動】phase-gate 會讀取 .kenaz/specialist/user_observations.jsonl(近 30 天)並 cross-reference ~/.kenaz/user_profile.json,輸出 dynamic_user_context。', '【Tier 3x — 自動】phase-gate 會自動處理 session handoff(per-session slots 於 .kenaz/specialist/handoffs/;legacy 單數檔僅剩 back-compat 清理,M3/TASK_1760 已退役),並設定 session_handoff_loaded=true/false。代理只需使用輸出結果,不需手動重做。', '輸出詩歌 + episode 摘要(按 cli_format,禁止自行重組格式)', '若 dynamic_user_context.significant_change_detected=true → 在詩歌下方輸出 1-2 行 user insight', '若 identity_recalled=true → 在詩歌下方輸出 🧠 自我認識 區塊(日期+lesson,最多 3 條)'],
|
|
360
|
+
1: ['執行 synapse_recall 並記錄結果 (recall_executed=true)', '輸出需求摘要 (chief_complaint)', '等待用戶確認理解'],
|
|
361
|
+
2: ['執行 synapse_recall 並用 Grep/Glob 調研(呼叫者用 kenaz_lsp_callers)', '記錄調研摘要 (research_notes)'],
|
|
362
|
+
3: ['設計任務拆解表格', '等待用戶批准'],
|
|
363
|
+
4: ['撰寫任務規格'],
|
|
364
|
+
5: ['驗證任務規格完整性'],
|
|
365
|
+
6: ['輸出完整任務摘要 + 時間估算', '等待用戶批准建立任務'],
|
|
366
|
+
7: ['建立所有任務卡'],
|
|
367
|
+
8: ['確認 dispatch 計劃', '等待用戶批准開始 dispatch'],
|
|
368
|
+
9: [
|
|
369
|
+
'驗證 agent 成果',
|
|
370
|
+
'執行 cognitive_checkpoint(rune/sigil 評估)',
|
|
371
|
+
'回傳 cognitive_checkpoint 結果 (cognitive_checkpoint={pre_code, post_code})',
|
|
372
|
+
],
|
|
373
|
+
10: ['執行 post-complete 流程'],
|
|
374
|
+
11: ['輸出最終結果', '等待用戶確認下一步'],
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
// ═══════════════════════════════════════
|
|
378
|
+
// Phase Exit Validation Rules
|
|
379
|
+
// ═══════════════════════════════════════
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Validator functions per required field.
|
|
383
|
+
* Returns true if the field is present and valid.
|
|
384
|
+
*/
|
|
385
|
+
const FIELD_VALIDATORS = {
|
|
386
|
+
episodes_checked: (data) => data.episodes_checked === true,
|
|
387
|
+
poem_displayed: (data) => data.poem_displayed === true,
|
|
388
|
+
ash_loaded: (data) => data.ash_loaded === true,
|
|
389
|
+
identity_recalled: (data) => data.identity_recalled === true || data.identity_recalled === false, // optional — non-blocking; true=found, false=none
|
|
390
|
+
motivational_state_loaded: (data) => data.motivational_state_loaded === true || data.motivational_state_loaded === false, // optional — non-blocking; true=file exists, false=not found
|
|
391
|
+
ansuz_motivational_loaded: (data) => data.ansuz_motivational_loaded === true || data.ansuz_motivational_loaded === false, // optional — non-blocking; true=file exists, false=not found
|
|
392
|
+
session_handoff_loaded: (data) => data.session_handoff_loaded === true || data.session_handoff_loaded === false, // required in Phase 0 so Tier 3x cannot be silently skipped
|
|
393
|
+
self_model_loaded: (data) => data.self_model_loaded === true || data.self_model_loaded === false, // optional — non-blocking; true=file exists, false=not found (cold start safe)
|
|
394
|
+
recall_executed: (data) => data.recall_executed === true || Array.isArray(data.recall_results),
|
|
395
|
+
cognitive_checkpoint: (data) =>
|
|
396
|
+
data.cognitive_checkpoint !== null &&
|
|
397
|
+
data.cognitive_checkpoint !== undefined &&
|
|
398
|
+
typeof data.cognitive_checkpoint === 'object',
|
|
399
|
+
// COG-DDX-001 enforcement: non-trivial problems require 3+ competing hypotheses
|
|
400
|
+
ddx_hypotheses: (data) =>
|
|
401
|
+
data.trivial_scope === true ||
|
|
402
|
+
(Array.isArray(data.ddx_hypotheses) && data.ddx_hypotheses.length >= 3),
|
|
403
|
+
// TASK_1104: boot ritual — consent + mirror + inheritance
|
|
404
|
+
consent_uttered: (data) => data.consent_uttered === true,
|
|
405
|
+
// consent_string must be the exact literal phrase — no emergency override
|
|
406
|
+
consent_string: (data) => typeof data.consent_string === 'string' && data.consent_string === ANSUZ_CONSENT_STRING,
|
|
407
|
+
// TASK_1113: mirror_acknowledged must be an object { id, understanding }
|
|
408
|
+
// where `id` is a blind-spot id (B-XXX) and `understanding` is a sentence
|
|
409
|
+
// (>=30 chars) that contains that blind-spot id. Booleans or bare strings
|
|
410
|
+
// no longer pass — letter-vs-spirit enforcement.
|
|
411
|
+
mirror_acknowledged: (data) => {
|
|
412
|
+
const m = data.mirror_acknowledged;
|
|
413
|
+
if (m === null || typeof m !== 'object' || Array.isArray(m)) return false;
|
|
414
|
+
if (typeof m.id !== 'string' || !/B-\d{3}/.test(m.id)) return false;
|
|
415
|
+
if (typeof m.understanding !== 'string') return false;
|
|
416
|
+
if (m.understanding.trim().length < 30) return false;
|
|
417
|
+
// understanding sentence must mention the blind-spot id literally
|
|
418
|
+
if (!m.understanding.includes(m.id)) return false;
|
|
419
|
+
return true;
|
|
420
|
+
},
|
|
421
|
+
inheritance_grounded: (data) => data.inheritance_grounded === true,
|
|
422
|
+
// TASK_1113: stdout markers must all have been emitted in the current
|
|
423
|
+
// conversation. The array argument is a self-report; transcript grep
|
|
424
|
+
// (verifyStdoutMarkersInTranscript) provides the ground-truth cross-check.
|
|
425
|
+
stdout_markers_emitted: (data) => {
|
|
426
|
+
// TASK_1170: use shared REQUIRED_STDOUT_MARKERS const (single source of truth)
|
|
427
|
+
if (!Array.isArray(data.stdout_markers_emitted)) return false;
|
|
428
|
+
return REQUIRED_STDOUT_MARKERS.every((m) => data.stdout_markers_emitted.includes(m));
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
// TASK_1113: transcript verification — grep recent conversation jsonl for
|
|
433
|
+
// the literal ritual markers. Defends against an agent passing the markers
|
|
434
|
+
// array without ever printing them. Best-effort: if no transcript can be
|
|
435
|
+
// located we DO NOT block (avoid false positives in headless/CI), but the
|
|
436
|
+
// warning is surfaced so Formation Lead / post-complete can audit.
|
|
437
|
+
function verifyStdoutMarkersInTranscript(requiredMarkers, lookbackLines = 200) {
|
|
438
|
+
// TASK_1170: result now includes counts{} — each marker → occurrences in tail
|
|
439
|
+
const result = { checked: false, missing: [], counts: {}, transcript: null, note: null };
|
|
440
|
+
try {
|
|
441
|
+
// Claude Code stores conversation jsonl under
|
|
442
|
+
// ~/.claude/projects/{encoded_cwd}/{conversation_id}.jsonl
|
|
443
|
+
// Walk that directory, pick the newest file, grep last N lines.
|
|
444
|
+
const home = process.env.USERPROFILE || process.env.HOME;
|
|
445
|
+
if (!home) {
|
|
446
|
+
result.note = 'no HOME/USERPROFILE';
|
|
447
|
+
return result;
|
|
448
|
+
}
|
|
449
|
+
const projectsRoot = path.join(home, '.claude', 'projects');
|
|
450
|
+
if (!fs.existsSync(projectsRoot)) {
|
|
451
|
+
result.note = 'projects dir missing';
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
// Encoded project path: Claude Code emits one '-' per separator char
|
|
455
|
+
// (NOT collapsed). On Windows `C:\` produces three chars → 'C--'.
|
|
456
|
+
// Using `[\\/:]+` here would fold ':\\' into a single dash and miss the
|
|
457
|
+
// exact directory entirely, forcing the fallback scan to pick a stale
|
|
458
|
+
// worktree jsonl. Match each separator individually instead.
|
|
459
|
+
const encodedCwd = BASE_DIR.replace(/[\\/:]/g, '-').replace(/^-+/, 'C--');
|
|
460
|
+
// Prefer the exact encodedCwd directory. Only fall back to scanning all
|
|
461
|
+
// other project dirs if the exact dir has no jsonl at all — otherwise a
|
|
462
|
+
// concurrently-active worktree session's newer jsonl can shadow our own.
|
|
463
|
+
const exactDir = path.join(projectsRoot, encodedCwd);
|
|
464
|
+
let newest = null;
|
|
465
|
+
const scanDir = (dir) => {
|
|
466
|
+
if (!fs.existsSync(dir)) return;
|
|
467
|
+
for (const f of fs.readdirSync(dir)) {
|
|
468
|
+
if (!f.endsWith('.jsonl')) continue;
|
|
469
|
+
const full = path.join(dir, f);
|
|
470
|
+
const stat = fs.statSync(full);
|
|
471
|
+
if (!newest || stat.mtimeMs > newest.mtimeMs) {
|
|
472
|
+
newest = { path: full, mtimeMs: stat.mtimeMs };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
scanDir(exactDir);
|
|
477
|
+
if (!newest) {
|
|
478
|
+
for (const entry of fs.readdirSync(projectsRoot, { withFileTypes: true })) {
|
|
479
|
+
if (!entry.isDirectory()) continue;
|
|
480
|
+
const full = path.join(projectsRoot, entry.name);
|
|
481
|
+
if (full === exactDir) continue;
|
|
482
|
+
scanDir(full);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!newest) {
|
|
486
|
+
result.note = 'no transcript jsonl found';
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
result.transcript = newest.path;
|
|
490
|
+
const raw = fs.readFileSync(newest.path, 'utf8');
|
|
491
|
+
const lines = raw.split(/\r?\n/);
|
|
492
|
+
const tail = lines.slice(-lookbackLines).join('\n');
|
|
493
|
+
result.checked = true;
|
|
494
|
+
// TASK_1170: compute per-marker occurrence counts for actionable error hints
|
|
495
|
+
result.counts = {};
|
|
496
|
+
for (const m of requiredMarkers) {
|
|
497
|
+
result.counts[m] = tail.split(m).length - 1;
|
|
498
|
+
}
|
|
499
|
+
result.missing = requiredMarkers.filter((m) => result.counts[m] === 0);
|
|
500
|
+
return result;
|
|
501
|
+
} catch (e) {
|
|
502
|
+
result.note = `transcript grep error: ${e.message}`;
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// TASK_1190: ritual_log fallback reader — when transcript grep fails to locate
|
|
508
|
+
// all 4 markers, read ansuz_ritual_log.jsonl and accept exit if all 4 expected
|
|
509
|
+
// stages (canary, consent, mirror, inheritance) have outcome=completed entries
|
|
510
|
+
// within the current session window.
|
|
511
|
+
//
|
|
512
|
+
// Session window anchor: ritual_state.json.completed_at ISO timestamp.
|
|
513
|
+
// If ritual_state.json is missing or unreadable, falls back to a conservative
|
|
514
|
+
// 60-second window before the current exit call.
|
|
515
|
+
function verifyStdoutMarkersViaRitualLog(requiredMarkers) {
|
|
516
|
+
const result = { accepted: false, confirmed: [], missing: [], windowAnchor: null, note: null };
|
|
517
|
+
try {
|
|
518
|
+
// Determine session window floor from ritual_state.json
|
|
519
|
+
let windowFloor = null;
|
|
520
|
+
const RITUAL_STATE_PATH = path.join(BASE_DIR, '.kenaz', 'specialist', 'ritual_state.json');
|
|
521
|
+
try {
|
|
522
|
+
if (fs.existsSync(RITUAL_STATE_PATH)) {
|
|
523
|
+
const rs = JSON.parse(fs.readFileSync(RITUAL_STATE_PATH, 'utf8'));
|
|
524
|
+
if (rs && rs.completed_at) {
|
|
525
|
+
windowFloor = new Date(rs.completed_at).getTime();
|
|
526
|
+
result.windowAnchor = rs.completed_at;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
} catch { /* ignore; fall through to default */ }
|
|
530
|
+
if (!windowFloor) {
|
|
531
|
+
windowFloor = Date.now() - 60_000;
|
|
532
|
+
result.windowAnchor = new Date(windowFloor).toISOString() + ' (60s default)';
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Read ritual log
|
|
536
|
+
if (!fs.existsSync(RITUAL_LOG_PATH)) {
|
|
537
|
+
result.note = 'ritual_log missing';
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
const raw = fs.readFileSync(RITUAL_LOG_PATH, 'utf8');
|
|
541
|
+
const entries = raw.split(/\r?\n/).filter(Boolean).map((line) => {
|
|
542
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
543
|
+
}).filter(Boolean);
|
|
544
|
+
|
|
545
|
+
// Determine expected stages from requiredMarkers via MARKER_TO_STAGE
|
|
546
|
+
const expectedStages = requiredMarkers.map((m) => MARKER_TO_STAGE[m]).filter(Boolean);
|
|
547
|
+
|
|
548
|
+
// Collect stages present in ritual_log within session window with outcome=completed
|
|
549
|
+
const completedStages = new Set(
|
|
550
|
+
entries
|
|
551
|
+
.filter((e) => e.timestamp && new Date(e.timestamp).getTime() >= windowFloor)
|
|
552
|
+
.filter((e) => e.outcome === 'completed')
|
|
553
|
+
.map((e) => e.stage)
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
result.confirmed = expectedStages.filter((s) => completedStages.has(s));
|
|
557
|
+
result.missing = expectedStages.filter((s) => !completedStages.has(s));
|
|
558
|
+
result.accepted = result.missing.length === 0 && expectedStages.length > 0;
|
|
559
|
+
return result;
|
|
560
|
+
} catch (e) {
|
|
561
|
+
result.note = `ritual_log fallback error: ${e.message}`;
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const PHASE_EXIT_RULES = {
|
|
567
|
+
0: {
|
|
568
|
+
required: [
|
|
569
|
+
'poem_displayed',
|
|
570
|
+
'ash_loaded',
|
|
571
|
+
'session_handoff_loaded',
|
|
572
|
+
// TASK_1104: boot ritual enforcement — no emergency override
|
|
573
|
+
'consent_uttered',
|
|
574
|
+
'consent_string',
|
|
575
|
+
'mirror_acknowledged',
|
|
576
|
+
'inheritance_grounded',
|
|
577
|
+
// TASK_1113: self-reported ritual markers must be present AND grep-
|
|
578
|
+
// verifiable in the live conversation transcript.
|
|
579
|
+
'stdout_markers_emitted',
|
|
580
|
+
],
|
|
581
|
+
description: 'Phase 0 must confirm: (1) Ash runes loaded; (2) poem displayed; (3) session handoff checked; (4) TASK_1104/1113 boot ritual: consent_uttered=true + consent_string exactly "' + ANSUZ_CONSENT_STRING + '" + mirror_acknowledged is an object {id:"B-XXX", understanding:"…"} (understanding >=30 chars, must contain blind-spot id) — booleans no longer pass (mirror_acknowledged must be object) — + inheritance_grounded=true + stdout_markers_emitted array containing RITUAL::THRESHOLD::CANARY_OK, RITUAL::INHERITANCE::LINEAGE_CLAIMED, RITUAL::INHERITANCE::MIRROR_ACCEPTED, RITUAL::STILLNESS::CONSENT_EMITTED. Ritual failures fix the ritual — no emergency override.',
|
|
582
|
+
},
|
|
583
|
+
1: {
|
|
584
|
+
required: ['recall_executed', 'ddx_hypotheses'],
|
|
585
|
+
description: 'Phase 1 must prove: (1) synapse recall performed (recall_executed=true or recall_results array), (2) COG-DDX-001: ddx_hypotheses array with >=3 entries OR trivial_scope=true. See .rule/general/ddx-synapse-stack.yaml',
|
|
586
|
+
},
|
|
587
|
+
9: {
|
|
588
|
+
required: ['cognitive_checkpoint'],
|
|
589
|
+
description: 'Phase 9 must have cognitive_checkpoint object (pre_code + post_code verdicts)',
|
|
590
|
+
trackFix: true, // increment vitals.fix_count when data.is_fix_task === true
|
|
591
|
+
},
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
// ═══════════════════════════════════════
|
|
595
|
+
// Chart Schema & CRUD
|
|
596
|
+
// ═══════════════════════════════════════
|
|
597
|
+
|
|
598
|
+
// TASK_2163: chart path is project-identity-aware (shared resolver) so
|
|
599
|
+
// sub-projects reusing bare TASK_ ids no longer collide in the flat core
|
|
600
|
+
// sessions dir. Core project keeps today's unprefixed filename.
|
|
601
|
+
function getChartPath(taskId) {
|
|
602
|
+
return chartPathLib.getChartPath(taskId, { cliProjectPath: CLI_PROJECT_PATH, argv: process.argv.slice(2) });
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function getChartReadPath(taskId) {
|
|
606
|
+
return chartPathLib.getChartReadPath(taskId, { cliProjectPath: CLI_PROJECT_PATH, argv: process.argv.slice(2) });
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
function createChart(taskId) {
|
|
610
|
+
return {
|
|
611
|
+
task_id: taskId,
|
|
612
|
+
created_at: new Date().toISOString(),
|
|
613
|
+
updated_at: new Date().toISOString(),
|
|
614
|
+
chief_complaint: null,
|
|
615
|
+
history: {
|
|
616
|
+
recall_results: [],
|
|
617
|
+
research_notes: '',
|
|
618
|
+
},
|
|
619
|
+
phases: {},
|
|
620
|
+
vitals: {
|
|
621
|
+
fix_count: 0,
|
|
622
|
+
modules_ever_loaded: [],
|
|
623
|
+
warnings: [],
|
|
624
|
+
},
|
|
625
|
+
progress_notes: [], // reserved for TASK_579
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function readChart(taskId) {
|
|
630
|
+
const chartPath = getChartReadPath(taskId);
|
|
631
|
+
if (!fs.existsSync(chartPath)) {
|
|
632
|
+
return createChart(taskId);
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
return JSON.parse(fs.readFileSync(chartPath, 'utf8'));
|
|
636
|
+
} catch (e) {
|
|
637
|
+
throw new Error(`Failed to parse chart for ${taskId}: ${e.message}`);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function writeChart(chart) {
|
|
642
|
+
const chartPath = getChartPath(chart.task_id);
|
|
643
|
+
chartPathLib.ensureChartDir(chartPath);
|
|
644
|
+
chart.updated_at = new Date().toISOString();
|
|
645
|
+
fs.writeFileSync(chartPath, JSON.stringify(chart, null, 2), 'utf8');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// ═══════════════════════════════════════
|
|
649
|
+
// Module Loading
|
|
650
|
+
// ═══════════════════════════════════════
|
|
651
|
+
|
|
652
|
+
function loadModule(moduleName) {
|
|
653
|
+
const relPath = MODULE_PATHS[moduleName];
|
|
654
|
+
if (!relPath) {
|
|
655
|
+
return { name: moduleName, error: `Module '${moduleName}' not in MODULE_PATHS registry` };
|
|
656
|
+
}
|
|
657
|
+
const absPath = path.join(BASE_DIR, relPath);
|
|
658
|
+
if (!fs.existsSync(absPath)) {
|
|
659
|
+
return {
|
|
660
|
+
name: moduleName,
|
|
661
|
+
warning: `Module file not found: ${relPath} (expected after TASK_576)`,
|
|
662
|
+
content: null,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
try {
|
|
666
|
+
const content = fs.readFileSync(absPath, 'utf8');
|
|
667
|
+
return { name: moduleName, path: relPath, content };
|
|
668
|
+
} catch (e) {
|
|
669
|
+
return { name: moduleName, error: `Failed to read ${relPath}: ${e.message}` };
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Decide which modules to load for a given phase + chart context.
|
|
675
|
+
* Context-driven rules:
|
|
676
|
+
* - fix_count >= 2 → inject loop_guard (prevent runaway fix loops)
|
|
677
|
+
*/
|
|
678
|
+
function decideModules(phase, chart) {
|
|
679
|
+
const modules = [...(PHASE_DEFAULT_MODULES[phase] || [])];
|
|
680
|
+
|
|
681
|
+
if (chart.vitals.fix_count >= 2 && !modules.includes('loop_guard')) {
|
|
682
|
+
modules.push('loop_guard');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Deduplicate
|
|
686
|
+
return [...new Set(modules)];
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
// ═══════════════════════════════════════
|
|
691
|
+
// Phase Instruction Loader (TASK_583)
|
|
692
|
+
// ═══════════════════════════════════════
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Extract a simple scalar value from YAML content.
|
|
696
|
+
* Handles: key: "value" or key: value
|
|
697
|
+
*/
|
|
698
|
+
function extractScalar(content, key) {
|
|
699
|
+
const re = new RegExp(`^${key}:\\s*(.+?)\\s*$`, 'm');
|
|
700
|
+
const match = content.match(re);
|
|
701
|
+
if (!match) return null;
|
|
702
|
+
return match[1].replace(/^["']|["']$/g, '').trim();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Extract a block scalar (|) value from YAML content.
|
|
707
|
+
* Handles: key: |\n line1\n line2
|
|
708
|
+
*/
|
|
709
|
+
function extractBlockScalar(content, key) {
|
|
710
|
+
// Find the block scalar header line
|
|
711
|
+
const headerRe = new RegExp(`^${key}:\\s*\\|[^\\n]*\\n`, 'm');
|
|
712
|
+
const headerMatch = content.match(headerRe);
|
|
713
|
+
if (!headerMatch) return null;
|
|
714
|
+
|
|
715
|
+
const startIdx = headerMatch.index + headerMatch[0].length;
|
|
716
|
+
const remaining = content.slice(startIdx).split('\n');
|
|
717
|
+
|
|
718
|
+
// Determine block indentation from first non-empty line
|
|
719
|
+
let blockIndent = null;
|
|
720
|
+
const blockLines = [];
|
|
721
|
+
for (const line of remaining) {
|
|
722
|
+
if (line.trim() === '') {
|
|
723
|
+
blockLines.push(''); // empty lines are part of the block
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
const indent = line.match(/^(\s*)/)[1].length;
|
|
727
|
+
if (blockIndent === null) blockIndent = indent;
|
|
728
|
+
if (indent < blockIndent) break; // dedented = end of block
|
|
729
|
+
blockLines.push(line);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Strip trailing empty lines, then strip base indent
|
|
733
|
+
while (blockLines.length > 0 && blockLines[blockLines.length - 1] === '') blockLines.pop();
|
|
734
|
+
if (blockIndent === null) return null;
|
|
735
|
+
return blockLines.map(l => l === '' ? '' : l.slice(blockIndent)).join('\n');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Extract a block list from YAML content.
|
|
740
|
+
* Handles: key:\n - item1\n - item2
|
|
741
|
+
*/
|
|
742
|
+
function extractBlockList(content, key) {
|
|
743
|
+
const re = new RegExp(`^${key}:\\s*\\n((?:[ \\t]+-[^\\n]*\\n?)*)`, 'm');
|
|
744
|
+
const match = content.match(re);
|
|
745
|
+
if (!match) return [];
|
|
746
|
+
return match[1].split('\n')
|
|
747
|
+
.map(l => l.trim())
|
|
748
|
+
.filter(l => l.startsWith('- '))
|
|
749
|
+
.map(l => l.slice(2).replace(/^["']|["']$/g, '').trim())
|
|
750
|
+
.filter(Boolean);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
/**
|
|
754
|
+
* Extract context_hints.key_data from phase YAML content.
|
|
755
|
+
* Supports inline array: key_data: ["a", "b"]
|
|
756
|
+
* Supports block list: key_data:\n - a\n - b
|
|
757
|
+
* Supports null: key_data: null
|
|
758
|
+
*/
|
|
759
|
+
function extractKeyData(content) {
|
|
760
|
+
// Inline array: key_data: ["a", "b"]
|
|
761
|
+
const inlineMatch = content.match(/key_data:\s*\[([^\]]*)\]/);
|
|
762
|
+
if (inlineMatch) {
|
|
763
|
+
if (!inlineMatch[1].trim()) return [];
|
|
764
|
+
return inlineMatch[1].split(',')
|
|
765
|
+
.map(s => s.trim().replace(/^["']|["']$/g, ''))
|
|
766
|
+
.filter(Boolean);
|
|
767
|
+
}
|
|
768
|
+
// Null: key_data: null
|
|
769
|
+
if (/key_data:\s*null/.test(content)) return null;
|
|
770
|
+
// Block list under key_data (indented)
|
|
771
|
+
const blockMatch = content.match(/key_data:\s*\n((?:[ \t]+-[^\n]*\n?)*)/);
|
|
772
|
+
if (blockMatch) {
|
|
773
|
+
return blockMatch[1].split('\n')
|
|
774
|
+
.map(l => l.trim())
|
|
775
|
+
.filter(l => l.startsWith('- '))
|
|
776
|
+
.map(l => l.slice(2).replace(/^["']|["']$/g, '').trim())
|
|
777
|
+
.filter(Boolean);
|
|
778
|
+
}
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Load phase YAML from phases/phase_N.yaml.
|
|
784
|
+
* Returns { content, name, goal, checklist, context_hints_raw, warning }
|
|
785
|
+
* Gracefully degrades if phases/ or the file doesn't exist.
|
|
786
|
+
*/
|
|
787
|
+
function loadPhaseInstructions(phase) {
|
|
788
|
+
if (!fs.existsSync(PHASES_DIR)) {
|
|
789
|
+
return {
|
|
790
|
+
content: null,
|
|
791
|
+
name: null,
|
|
792
|
+
goal: null,
|
|
793
|
+
checklist: [],
|
|
794
|
+
context_hints_raw: null,
|
|
795
|
+
warning: `phases/ directory not found — phase instructions unavailable (graceful degradation)`,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
const phaseFile = path.join(PHASES_DIR, `phase_${phase}.yaml`);
|
|
799
|
+
if (!fs.existsSync(phaseFile)) {
|
|
800
|
+
return {
|
|
801
|
+
content: null,
|
|
802
|
+
name: null,
|
|
803
|
+
goal: null,
|
|
804
|
+
checklist: [],
|
|
805
|
+
context_hints_raw: null,
|
|
806
|
+
warning: `phase_${phase}.yaml not found — phase instructions unavailable`,
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
try {
|
|
810
|
+
const content = fs.readFileSync(phaseFile, 'utf8');
|
|
811
|
+
const name = extractScalar(content, 'name');
|
|
812
|
+
const goal = extractScalar(content, 'goal');
|
|
813
|
+
const checklist = extractBlockList(content, 'checklist');
|
|
814
|
+
const keyData = extractKeyData(content);
|
|
815
|
+
const inputFrom = extractScalar(content, 'input_from');
|
|
816
|
+
const poem = extractBlockScalar(content, 'poem');
|
|
817
|
+
return {
|
|
818
|
+
content,
|
|
819
|
+
name,
|
|
820
|
+
goal,
|
|
821
|
+
checklist,
|
|
822
|
+
poem: poem || null,
|
|
823
|
+
context_hints_raw: { key_data: keyData, input_from: inputFrom },
|
|
824
|
+
warning: null,
|
|
825
|
+
};
|
|
826
|
+
} catch (e) {
|
|
827
|
+
return {
|
|
828
|
+
content: null,
|
|
829
|
+
name: null,
|
|
830
|
+
goal: null,
|
|
831
|
+
checklist: [],
|
|
832
|
+
context_hints_raw: null,
|
|
833
|
+
warning: `Failed to read phase_${phase}.yaml: ${e.message}`,
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* Build context hints for phase N by reading the previous phase's data from chart.
|
|
840
|
+
* Reads context_hints.key_data from phase_N.yaml and extracts those keys from
|
|
841
|
+
* chart.phases[sourcePhasNum].data
|
|
842
|
+
* Returns { [key]: value, ... } or {}
|
|
843
|
+
*/
|
|
844
|
+
function buildContextHints(phase, chart, phaseInstructions, warnings) {
|
|
845
|
+
if (!phaseInstructions || !phaseInstructions.context_hints_raw) return {};
|
|
846
|
+
const { key_data, input_from } = phaseInstructions.context_hints_raw;
|
|
847
|
+
if (!key_data || key_data.length === 0) return {};
|
|
848
|
+
|
|
849
|
+
// Determine source phase number (default: phase - 1, overridden by input_from)
|
|
850
|
+
let sourcePhasNum = phase - 1;
|
|
851
|
+
if (input_from) {
|
|
852
|
+
const m = String(input_from).match(/(\d+)/);
|
|
853
|
+
if (m) sourcePhasNum = parseInt(m[1], 10);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const sourceData = (chart.phases[String(sourcePhasNum)] || {}).data || {};
|
|
857
|
+
const hints = {};
|
|
858
|
+
for (const key of key_data) {
|
|
859
|
+
hints[key] = key in sourceData ? sourceData[key] : null;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
if (Object.keys(hints).length > 0 && Object.values(hints).every(v => v === null)) {
|
|
863
|
+
warnings.push(`context_hints: phase ${sourcePhasNum} data is empty — hints are null (agent may not have exited phase ${sourcePhasNum} yet)`);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
const phase9Hints = buildPhase9VerificationHints(phase, sourceData, warnings);
|
|
867
|
+
if (phase9Hints) {
|
|
868
|
+
hints.verification_diff_strategy = phase9Hints;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return hints;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
function firstNonEmptyString(...values) {
|
|
875
|
+
for (const value of values) {
|
|
876
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
877
|
+
}
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function branchFromExpected(expectedBranches, taskId, index) {
|
|
882
|
+
if (!expectedBranches) return null;
|
|
883
|
+
if (typeof expectedBranches === 'string') return expectedBranches.trim() || null;
|
|
884
|
+
if (Array.isArray(expectedBranches)) {
|
|
885
|
+
const candidate = expectedBranches[index];
|
|
886
|
+
return typeof candidate === 'string' && candidate.trim() ? candidate.trim() : null;
|
|
887
|
+
}
|
|
888
|
+
if (typeof expectedBranches === 'object') {
|
|
889
|
+
const candidate = taskId && typeof expectedBranches[taskId] === 'string'
|
|
890
|
+
? expectedBranches[taskId]
|
|
891
|
+
: null;
|
|
892
|
+
return candidate && candidate.trim() ? candidate.trim() : null;
|
|
893
|
+
}
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function buildPhase9VerificationHints(phase, sourceData, warnings) {
|
|
898
|
+
if (phase !== 9 || !sourceData || !Array.isArray(sourceData.dispatched_tasks)) return null;
|
|
899
|
+
|
|
900
|
+
const expectedBranches = sourceData.expected_branches;
|
|
901
|
+
const targets = sourceData.dispatched_tasks.map((task, index) => {
|
|
902
|
+
const taskObj = task && typeof task === 'object' ? task : {};
|
|
903
|
+
const taskId = firstNonEmptyString(taskObj.task_id, taskObj.id, taskObj.taskId, `task_${index + 1}`);
|
|
904
|
+
const branch = firstNonEmptyString(
|
|
905
|
+
taskObj.branch,
|
|
906
|
+
taskObj.branch_name,
|
|
907
|
+
taskObj.branchName,
|
|
908
|
+
taskObj.task_branch,
|
|
909
|
+
taskObj.taskBranch,
|
|
910
|
+
branchFromExpected(expectedBranches, taskId, index),
|
|
911
|
+
);
|
|
912
|
+
const commit = firstNonEmptyString(
|
|
913
|
+
taskObj.commit,
|
|
914
|
+
taskObj.commit_sha,
|
|
915
|
+
taskObj.commitSha,
|
|
916
|
+
taskObj.task_commit,
|
|
917
|
+
taskObj.taskCommit,
|
|
918
|
+
taskObj.head_commit,
|
|
919
|
+
taskObj.headCommit,
|
|
920
|
+
taskObj.head_sha,
|
|
921
|
+
taskObj.headSha,
|
|
922
|
+
taskObj.result && taskObj.result.commit,
|
|
923
|
+
taskObj.result && taskObj.result.commit_sha,
|
|
924
|
+
taskObj.result && taskObj.result.commitSha,
|
|
925
|
+
);
|
|
926
|
+
const baseBranch = firstNonEmptyString(
|
|
927
|
+
taskObj.base_branch,
|
|
928
|
+
taskObj.baseBranch,
|
|
929
|
+
taskObj.target_branch,
|
|
930
|
+
taskObj.targetBranch,
|
|
931
|
+
taskObj.user_branch,
|
|
932
|
+
taskObj.userBranch,
|
|
933
|
+
'main',
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
if (commit) {
|
|
937
|
+
return {
|
|
938
|
+
task_id: taskId,
|
|
939
|
+
strategy: 'single_commit',
|
|
940
|
+
command: `git show ${commit} --stat`,
|
|
941
|
+
branch,
|
|
942
|
+
commit,
|
|
943
|
+
rationale: 'Phase 9 should verify the task commit directly. HEAD..branch can pull in stale-base changes from the worktree and misreport the agent output.',
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
if (branch) {
|
|
948
|
+
return {
|
|
949
|
+
task_id: taskId,
|
|
950
|
+
strategy: 'three_dot',
|
|
951
|
+
command: `git diff ${baseBranch}...${branch} --stat`,
|
|
952
|
+
branch,
|
|
953
|
+
base_branch: baseBranch,
|
|
954
|
+
rationale: 'Fallback to three-dot diff when no task commit is recorded. The merge-base diff excludes unrelated history that HEAD..branch can over-report.',
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
warnings.push(`Phase 9: no commit or branch metadata found for dispatched task ${taskId} — verification diff hint omitted`);
|
|
959
|
+
return {
|
|
960
|
+
task_id: taskId,
|
|
961
|
+
strategy: 'unavailable',
|
|
962
|
+
command: null,
|
|
963
|
+
rationale: 'No task commit or branch metadata was recorded, so phase-gate could not build a safe verification diff command.',
|
|
964
|
+
};
|
|
965
|
+
});
|
|
966
|
+
|
|
967
|
+
return {
|
|
968
|
+
rationale: 'Prefer a single-commit view for Phase 9 verification. If only a branch is known, use a three-dot diff against the base branch instead of HEAD..branch.',
|
|
969
|
+
targets,
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Extract inject configuration from phase YAML content.
|
|
975
|
+
* Parses nested inject: block with rules: and specialist: sub-lists.
|
|
976
|
+
*
|
|
977
|
+
* YAML schema:
|
|
978
|
+
* inject:
|
|
979
|
+
* rules:
|
|
980
|
+
* - synapse-code-intelligence.yaml
|
|
981
|
+
* specialist:
|
|
982
|
+
* - capability.yaml#synapse_integration
|
|
983
|
+
*
|
|
984
|
+
* Returns { rules: string[], specialist: string[] }
|
|
985
|
+
*/
|
|
986
|
+
function extractInjectConfig(content) {
|
|
987
|
+
const result = { rules: [], specialist: [] };
|
|
988
|
+
|
|
989
|
+
const lines = content.split('\n');
|
|
990
|
+
let inInject = false;
|
|
991
|
+
const injectLines = [];
|
|
992
|
+
|
|
993
|
+
for (const line of lines) {
|
|
994
|
+
if (/^inject:\s*$/.test(line)) {
|
|
995
|
+
inInject = true;
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
if (inInject) {
|
|
999
|
+
// End inject block at next top-level key (non-whitespace start, non-empty)
|
|
1000
|
+
if (line.length > 0 && !/^\s/.test(line)) break;
|
|
1001
|
+
injectLines.push(line);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
if (injectLines.length === 0) return result;
|
|
1006
|
+
|
|
1007
|
+
const injectBlock = injectLines.join('\n');
|
|
1008
|
+
|
|
1009
|
+
function parseSubList(block, key) {
|
|
1010
|
+
const re = new RegExp(`^[ \\t]+${key}:[ \\t]*\\n((?:[ \\t]+-[^\\n]*\\n?)*)`, 'm');
|
|
1011
|
+
const m = block.match(re);
|
|
1012
|
+
if (!m) return [];
|
|
1013
|
+
return m[1].split('\n')
|
|
1014
|
+
.map(l => l.trim())
|
|
1015
|
+
.filter(l => l.startsWith('- '))
|
|
1016
|
+
.map(l => l.slice(2).replace(/^["']|["']$/g, '').trim())
|
|
1017
|
+
.filter(Boolean);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
result.rules = parseSubList(injectBlock, 'rules');
|
|
1021
|
+
result.specialist = parseSubList(injectBlock, 'specialist');
|
|
1022
|
+
return result;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Load injected rule/specialist files declared in a phase's inject config.
|
|
1027
|
+
* Returns { [filename]: { path, content } | { path, error } }
|
|
1028
|
+
*/
|
|
1029
|
+
function loadInjectedFiles(injectConfig, warnings) {
|
|
1030
|
+
const injected = {};
|
|
1031
|
+
|
|
1032
|
+
for (const ruleFile of injectConfig.rules) {
|
|
1033
|
+
const relPath = path.join('.rule', 'general', ruleFile);
|
|
1034
|
+
const absPath = path.join(BASE_DIR, relPath);
|
|
1035
|
+
try {
|
|
1036
|
+
const content = fs.readFileSync(absPath, 'utf8');
|
|
1037
|
+
injected[ruleFile] = { path: relPath.replace(/\\/g, '/'), content };
|
|
1038
|
+
} catch (e) {
|
|
1039
|
+
injected[ruleFile] = { path: relPath.replace(/\\/g, '/'), error: `Failed to read: ${e.message}` };
|
|
1040
|
+
warnings.push(`inject.rules: failed to load ${ruleFile}: ${e.message}`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
for (const specRef of injectConfig.specialist) {
|
|
1045
|
+
// specRef may be "file.yaml" or "file.yaml#section"
|
|
1046
|
+
const fileRef = specRef.split('#')[0];
|
|
1047
|
+
const relPath = path.join('.specialist', 'Ansuz', fileRef);
|
|
1048
|
+
const absPath = path.join(BASE_DIR, relPath);
|
|
1049
|
+
try {
|
|
1050
|
+
const content = fs.readFileSync(absPath, 'utf8');
|
|
1051
|
+
injected[specRef] = { path: relPath.replace(/\\/g, '/'), content };
|
|
1052
|
+
} catch (e) {
|
|
1053
|
+
injected[specRef] = { path: relPath.replace(/\\/g, '/'), error: `Failed to read: ${e.message}` };
|
|
1054
|
+
warnings.push(`inject.specialist: failed to load ${specRef}: ${e.message}`);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return injected;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Dump all phases from phases/ directory for debugging and documentation.
|
|
1063
|
+
* node phase-gate.js dump --all
|
|
1064
|
+
*/
|
|
1065
|
+
function dumpPhases() {
|
|
1066
|
+
if (!fs.existsSync(PHASES_DIR)) {
|
|
1067
|
+
return outputResult({
|
|
1068
|
+
action: 'dump',
|
|
1069
|
+
warning: `phases/ directory not found at ${PHASES_DIR}`,
|
|
1070
|
+
phases: [],
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
let files;
|
|
1075
|
+
try {
|
|
1076
|
+
files = fs.readdirSync(PHASES_DIR).filter(f => /^phase_\d+\.yaml$/.test(f));
|
|
1077
|
+
} catch (e) {
|
|
1078
|
+
return outputResult({ action: 'dump', error: `Failed to read phases/: ${e.message}`, phases: [] });
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
files.sort((a, b) => parseInt(a.match(/\d+/)[0]) - parseInt(b.match(/\d+/)[0]));
|
|
1082
|
+
|
|
1083
|
+
const phases = [];
|
|
1084
|
+
for (const file of files) {
|
|
1085
|
+
const phaseFile = path.join(PHASES_DIR, file);
|
|
1086
|
+
try {
|
|
1087
|
+
const content = fs.readFileSync(phaseFile, 'utf8');
|
|
1088
|
+
const phaseNum = parseInt(file.match(/\d+/)[0]);
|
|
1089
|
+
const name = extractScalar(content, 'name');
|
|
1090
|
+
const goal = extractScalar(content, 'goal');
|
|
1091
|
+
const checklist = extractBlockList(content, 'checklist');
|
|
1092
|
+
phases.push({ phase: phaseNum, name, goal, checklist });
|
|
1093
|
+
} catch (e) {
|
|
1094
|
+
phases.push({ phase: parseInt(file.match(/\d+/)[0]), error: e.message });
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
return outputResult({ action: 'dump', total: phases.length, phases });
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// ═══════════════════════════════════════
|
|
1102
|
+
// Progress Note Helpers (TASK_579)
|
|
1103
|
+
// ═══════════════════════════════════════
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Search for a task YAML file by task ID.
|
|
1107
|
+
* Checks both .collaboration/tasks/ and .kenaz/tasks/ directories.
|
|
1108
|
+
*/
|
|
1109
|
+
function findTaskFile(taskId) {
|
|
1110
|
+
const searchRoots = [
|
|
1111
|
+
path.join(BASE_DIR, '.collaboration', 'tasks'),
|
|
1112
|
+
path.join(BASE_DIR, '.kenaz', 'tasks'),
|
|
1113
|
+
];
|
|
1114
|
+
const statuses = ['active', 'done', 'backlog', 'blocked', 'cancel'];
|
|
1115
|
+
|
|
1116
|
+
for (const root of searchRoots) {
|
|
1117
|
+
if (!fs.existsSync(root)) continue;
|
|
1118
|
+
for (const status of statuses) {
|
|
1119
|
+
const dir = path.join(root, status);
|
|
1120
|
+
if (!fs.existsSync(dir)) continue;
|
|
1121
|
+
try {
|
|
1122
|
+
const files = fs.readdirSync(dir);
|
|
1123
|
+
const match = files.find(f => f.startsWith(taskId + '_') || f === taskId + '.yaml');
|
|
1124
|
+
if (match) return path.join(dir, match);
|
|
1125
|
+
} catch { /* skip */ }
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
return null;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* Extract a YAML list field from raw file content (no external deps).
|
|
1133
|
+
* Handles: field_name:
|
|
1134
|
+
* - item 1
|
|
1135
|
+
*/
|
|
1136
|
+
function extractYamlList(content, fieldName) {
|
|
1137
|
+
const escaped = fieldName.replace(/[.*+?^${}()|[\]\\]/g, String.raw`// ═══════════════════════════════════════
|
|
1138
|
+
// Enter Logic
|
|
1139
|
+
// ═══════════════════════════════════════`);
|
|
1140
|
+
const blockRe = new RegExp(escaped + ':[ \\t]*\\n((?:[ \\t]+-[^\\n]*\\n?)*)', 'm');
|
|
1141
|
+
const match = content.match(blockRe);
|
|
1142
|
+
if (!match) return [];
|
|
1143
|
+
return match[1].split('\n').map(line => line.trim()).filter(line => line.startsWith('- ')).map(line => line.slice(2).replace(/^['"]|['"]$/g, '').trim()).filter(Boolean);
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
function readTaskScopePaths(taskId) {
|
|
1147
|
+
try {
|
|
1148
|
+
const file = findTaskFile(taskId);
|
|
1149
|
+
if (!file) return [];
|
|
1150
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
1151
|
+
return extractYamlList(raw, 'scope_paths');
|
|
1152
|
+
} catch {
|
|
1153
|
+
return [];
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
function findActiveOrBacklogTaskFile(taskId) {
|
|
1158
|
+
const tasksRoot = resolveKenazPath('tasks');
|
|
1159
|
+
for (const status of ['active', 'backlog']) {
|
|
1160
|
+
const dir = path.join(tasksRoot, status);
|
|
1161
|
+
if (!fs.existsSync(dir)) continue;
|
|
1162
|
+
try {
|
|
1163
|
+
const files = fs.readdirSync(dir);
|
|
1164
|
+
const match = files.find(f => f.startsWith(taskId + '_') || f === taskId + '.yaml');
|
|
1165
|
+
if (match) return path.join(dir, match);
|
|
1166
|
+
} catch { /* skip unreadable task dirs */ }
|
|
1167
|
+
}
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
function extractYamlScalar(content, fieldName) {
|
|
1172
|
+
const escaped = fieldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
1173
|
+
const re = new RegExp(`^${escaped}:[ \\t]*(.*?)\\s*$`, 'm');
|
|
1174
|
+
const match = content.match(re);
|
|
1175
|
+
if (!match) return null;
|
|
1176
|
+
const value = match[1].replace(/^['"]|['"]$/g, '').trim();
|
|
1177
|
+
return value.length > 0 ? value : null;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function readTaskForCompressionFallback(taskId, warnings) {
|
|
1181
|
+
const file = findActiveOrBacklogTaskFile(taskId);
|
|
1182
|
+
if (!file) {
|
|
1183
|
+
warnings.push(`Phase 8 compression_fallback: task YAML not found for ${taskId} in active/backlog — skipped`);
|
|
1184
|
+
return null;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
try {
|
|
1188
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
1189
|
+
const scopePaths = extractYamlList(raw, 'scope_paths');
|
|
1190
|
+
if (scopePaths.length === 0) {
|
|
1191
|
+
warnings.push(`Phase 8 compression_fallback: task ${taskId} has no scope_paths — skipped`);
|
|
1192
|
+
return null;
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
const dependencyList = extractYamlList(raw, 'dependencies');
|
|
1196
|
+
const dependencyScalar = extractYamlScalar(raw, 'dependencies');
|
|
1197
|
+
return {
|
|
1198
|
+
task_id: taskId,
|
|
1199
|
+
scope_paths: scopePaths,
|
|
1200
|
+
rigor: extractYamlScalar(raw, 'rigor') || 'medium',
|
|
1201
|
+
dependencies: dependencyList.length > 0
|
|
1202
|
+
? dependencyList
|
|
1203
|
+
: (dependencyScalar && dependencyScalar !== 'null' ? [dependencyScalar] : []),
|
|
1204
|
+
};
|
|
1205
|
+
} catch (e) {
|
|
1206
|
+
warnings.push(`Phase 8 compression_fallback: failed to read task YAML for ${taskId}: ${e.message}`);
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function readProjectYaml() {
|
|
1212
|
+
const projectYamlPath = resolveKenazPath('project.yaml');
|
|
1213
|
+
if (!fs.existsSync(projectYamlPath)) return null;
|
|
1214
|
+
try {
|
|
1215
|
+
return fs.readFileSync(projectYamlPath, 'utf8');
|
|
1216
|
+
} catch {
|
|
1217
|
+
return null;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function isPhase0GitContextDisabled(projectYamlContent) {
|
|
1222
|
+
if (!projectYamlContent || typeof projectYamlContent !== 'string') return false;
|
|
1223
|
+
const phase0Match = projectYamlContent.match(/^phase0:\s*\n((?:[ \t]+[^\n]*\n?)*)/m);
|
|
1224
|
+
if (!phase0Match) return false;
|
|
1225
|
+
return /^[ \t]+git_context:\s*disabled\s*$/m.test(phase0Match[1]);
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
function getChartPhaseRecord(chart, phase) {
|
|
1229
|
+
const key = String(phase);
|
|
1230
|
+
return (chart && chart.phases && chart.phases[key]) || (chart && chart[`phase_${key}`]) || null;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function getPhaseData(phaseRecord) {
|
|
1234
|
+
return phaseRecord && phaseRecord.data && typeof phaseRecord.data === 'object'
|
|
1235
|
+
? phaseRecord.data
|
|
1236
|
+
: {};
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
function hasPredictionForTask(taskId) {
|
|
1240
|
+
try {
|
|
1241
|
+
if (!fs.existsSync(PREDICTIONS_PATH)) return false;
|
|
1242
|
+
const parsed = JSON.parse(fs.readFileSync(PREDICTIONS_PATH, 'utf8'));
|
|
1243
|
+
return !!(parsed && Array.isArray(parsed.predictions) &&
|
|
1244
|
+
parsed.predictions.some(p => p && p.task_id === taskId));
|
|
1245
|
+
} catch {
|
|
1246
|
+
return false;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
function shouldWriteCompressionFallback(chart, taskId) {
|
|
1251
|
+
if (!taskId) return false;
|
|
1252
|
+
const phase3 = getChartPhaseRecord(chart, 3);
|
|
1253
|
+
const phase3Data = getPhaseData(phase3);
|
|
1254
|
+
|
|
1255
|
+
if (phase3Data.design_inputs_recorded === true || (phase3 && phase3.design_inputs_recorded === true)) return false;
|
|
1256
|
+
if (phase3 && phase3Data.predictions_recorded && Number(phase3Data.predictions_recorded) > 0) return false;
|
|
1257
|
+
if (phase3 && phase3.predictions_recorded && Number(phase3.predictions_recorded) > 0) return false;
|
|
1258
|
+
|
|
1259
|
+
return !hasPredictionForTask(taskId);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
function emitCompressionFallbackIfNeeded(chart, taskId, warnings) {
|
|
1263
|
+
if (!shouldWriteCompressionFallback(chart, taskId)) return false;
|
|
1264
|
+
|
|
1265
|
+
const task = readTaskForCompressionFallback(taskId, warnings);
|
|
1266
|
+
if (!task) return false;
|
|
1267
|
+
|
|
1268
|
+
writePredictions([{
|
|
1269
|
+
task_id: taskId,
|
|
1270
|
+
scope_paths: task.scope_paths,
|
|
1271
|
+
risk: task.rigor,
|
|
1272
|
+
source: 'compression_fallback',
|
|
1273
|
+
confidence: 0.5,
|
|
1274
|
+
predicted_health_delta: null,
|
|
1275
|
+
}], warnings);
|
|
1276
|
+
|
|
1277
|
+
try {
|
|
1278
|
+
const dr = require(path.join(BASE_DIR, '.specialist', 'tools', 'design-reasoning.js'));
|
|
1279
|
+
dr.recordDesignReasoning({
|
|
1280
|
+
taskId,
|
|
1281
|
+
principlesApplied: [],
|
|
1282
|
+
guardsApplied: [],
|
|
1283
|
+
formationSelected: 'compression_fallback',
|
|
1284
|
+
subtaskCount: 1,
|
|
1285
|
+
splitRationale: null,
|
|
1286
|
+
dependencyAssumptions: task.dependencies,
|
|
1287
|
+
riskAssessment: task.rigor,
|
|
1288
|
+
notes: 'synthesized at Phase 8 enter — Phase 3 was compressed',
|
|
1289
|
+
source: 'compression_fallback',
|
|
1290
|
+
});
|
|
1291
|
+
} catch (e) {
|
|
1292
|
+
warnings.push(`Phase 8 compression_fallback: failed to record design reasoning: ${e.message}`);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
warnings.push(`Phase 8 compression_fallback: synthesized minimal prediction for ${taskId}`);
|
|
1296
|
+
return true;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
/**
|
|
1300
|
+
* Count word overlap between two strings (case-insensitive, for fuzzy AC matching).
|
|
1301
|
+
*/
|
|
1302
|
+
function wordOverlap(a, b) {
|
|
1303
|
+
const stopWords = new Set(['the','a','an','is','are','to','of','and','in','for','on','at','by','with','from']);
|
|
1304
|
+
const tokenize = s => s.toLowerCase().replace(/[^a-z0-9\s]/g, ' ').split(/\s+/).filter(w => w.length > 2 && !stopWords.has(w));
|
|
1305
|
+
const wordsA = new Set(tokenize(a));
|
|
1306
|
+
const wordsB = tokenize(b);
|
|
1307
|
+
return wordsB.filter(w => wordsA.has(w)).length;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
/**
|
|
1311
|
+
* Validate progress notes in a chart against task acceptance_criteria + scope_paths.
|
|
1312
|
+
* Returns structured result embedded in Phase 9 data.
|
|
1313
|
+
* Non-blocking: scope_violations and cognitive_gap produce warnings only.
|
|
1314
|
+
*
|
|
1315
|
+
* @param {object} chart - parsed chart JSON
|
|
1316
|
+
* @param {string} taskId - task ID
|
|
1317
|
+
* @returns {{ validation: object, warnings: string[] }}
|
|
1318
|
+
*/
|
|
1319
|
+
function validateProgressNotes(chart, taskId) {
|
|
1320
|
+
const warnings = [];
|
|
1321
|
+
const notes = chart.progress_notes || [];
|
|
1322
|
+
|
|
1323
|
+
if (notes.length === 0) {
|
|
1324
|
+
return {
|
|
1325
|
+
validation: {
|
|
1326
|
+
ac_pass_count: 0, ac_fail_count: 0, ac_partial_count: 0,
|
|
1327
|
+
ac_unmatched: [], scope_violations: [], deviations: [],
|
|
1328
|
+
cognitive_gap: false, note_count: 0, skipped: true,
|
|
1329
|
+
skip_reason: 'No progress notes found in chart',
|
|
1330
|
+
},
|
|
1331
|
+
warnings: ['Phase 9: No progress_notes in chart — agent may not have written a progress note'],
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
const taskFile = findTaskFile(taskId);
|
|
1336
|
+
let taskAcceptanceCriteria = [];
|
|
1337
|
+
let taskScopePaths = [];
|
|
1338
|
+
|
|
1339
|
+
if (taskFile) {
|
|
1340
|
+
try {
|
|
1341
|
+
const yamlContent = fs.readFileSync(taskFile, 'utf8');
|
|
1342
|
+
taskAcceptanceCriteria = extractYamlList(yamlContent, 'acceptance_criteria');
|
|
1343
|
+
taskScopePaths = extractYamlList(yamlContent, 'scope_paths');
|
|
1344
|
+
} catch (e) {
|
|
1345
|
+
warnings.push(`Phase 9: Failed to read task YAML at ${taskFile}: ${e.message}`);
|
|
1346
|
+
}
|
|
1347
|
+
} else {
|
|
1348
|
+
warnings.push(`Phase 9: Task YAML not found for ${taskId} — AC comparison skipped`);
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
let acPassCount = 0;
|
|
1352
|
+
let acFailCount = 0;
|
|
1353
|
+
let acPartialCount = 0;
|
|
1354
|
+
const acUnmatched = [];
|
|
1355
|
+
const allScopeViolations = [];
|
|
1356
|
+
const allDeviations = [];
|
|
1357
|
+
let cognitiveGap = false;
|
|
1358
|
+
|
|
1359
|
+
for (const note of notes) {
|
|
1360
|
+
const noteAC = Array.isArray(note.acceptance_criteria) ? note.acceptance_criteria : [];
|
|
1361
|
+
|
|
1362
|
+
if (taskAcceptanceCriteria.length > 0) {
|
|
1363
|
+
for (const taskCriterion of taskAcceptanceCriteria) {
|
|
1364
|
+
let bestMatch = null;
|
|
1365
|
+
let bestScore = 0;
|
|
1366
|
+
for (const noteEntry of noteAC) {
|
|
1367
|
+
const criterion = noteEntry.criterion || '';
|
|
1368
|
+
if (criterion.toLowerCase().includes(taskCriterion.toLowerCase().slice(0, 20))) {
|
|
1369
|
+
bestMatch = noteEntry; bestScore = 999; break;
|
|
1370
|
+
}
|
|
1371
|
+
const score = wordOverlap(taskCriterion, criterion);
|
|
1372
|
+
if (score > bestScore) { bestScore = score; bestMatch = noteEntry; }
|
|
1373
|
+
}
|
|
1374
|
+
if (bestMatch && bestScore >= 3) {
|
|
1375
|
+
const status = (bestMatch.status || '').toLowerCase();
|
|
1376
|
+
if (status === 'pass') acPassCount++;
|
|
1377
|
+
else if (status === 'fail') acFailCount++;
|
|
1378
|
+
else if (status === 'partial') acPartialCount++;
|
|
1379
|
+
else acUnmatched.push(taskCriterion);
|
|
1380
|
+
} else {
|
|
1381
|
+
acUnmatched.push(taskCriterion);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
} else {
|
|
1385
|
+
for (const entry of noteAC) {
|
|
1386
|
+
const status = (entry.status || '').toLowerCase();
|
|
1387
|
+
if (status === 'pass') acPassCount++;
|
|
1388
|
+
else if (status === 'fail') acFailCount++;
|
|
1389
|
+
else if (status === 'partial') acPartialCount++;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if (taskScopePaths.length > 0) {
|
|
1394
|
+
const changes = note.changes || {};
|
|
1395
|
+
const allFiles = [
|
|
1396
|
+
...(changes.files_created || []),
|
|
1397
|
+
...(changes.files_modified || []),
|
|
1398
|
+
...(changes.files_deleted || []),
|
|
1399
|
+
];
|
|
1400
|
+
for (const file of allFiles) {
|
|
1401
|
+
const normalFile = file.replace(/\\/g, '/');
|
|
1402
|
+
const inScope = taskScopePaths.some(sp => {
|
|
1403
|
+
const normalSp = sp.replace(/\\/g, '/').replace(/\/$/, '');
|
|
1404
|
+
return normalFile.startsWith(normalSp + '/') || normalFile === normalSp;
|
|
1405
|
+
});
|
|
1406
|
+
if (!inScope && !allScopeViolations.includes(normalFile)) {
|
|
1407
|
+
allScopeViolations.push(normalFile);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
const noteDeviations = note.deviations || [];
|
|
1413
|
+
for (const d of noteDeviations) {
|
|
1414
|
+
if (d && !allDeviations.includes(d)) allDeviations.push(d);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
const cogOut = note.cognitive_output || {};
|
|
1418
|
+
const runes = cogOut.runes || [];
|
|
1419
|
+
const sigils = cogOut.sigils || [];
|
|
1420
|
+
if (runes.length === 0 && sigils.length === 0) cognitiveGap = true;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
if (allScopeViolations.length > 0) {
|
|
1424
|
+
warnings.push(`Phase 9: scope_violations — agent modified files outside scope_paths: [${allScopeViolations.join(', ')}]. Ansuz should verify.`);
|
|
1425
|
+
}
|
|
1426
|
+
if (cognitiveGap) {
|
|
1427
|
+
warnings.push('Phase 9: cognitive_gap — no runes/sigils inscribed. Ansuz should evaluate if synapse_inscribe is warranted.');
|
|
1428
|
+
}
|
|
1429
|
+
if (acUnmatched.length > 0) {
|
|
1430
|
+
warnings.push(`Phase 9: ${acUnmatched.length} AC item(s) could not be matched in progress note.`);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
return {
|
|
1434
|
+
validation: {
|
|
1435
|
+
ac_pass_count: acPassCount,
|
|
1436
|
+
ac_fail_count: acFailCount,
|
|
1437
|
+
ac_partial_count: acPartialCount,
|
|
1438
|
+
ac_unmatched: acUnmatched,
|
|
1439
|
+
scope_violations: allScopeViolations,
|
|
1440
|
+
deviations: allDeviations,
|
|
1441
|
+
cognitive_gap: cognitiveGap,
|
|
1442
|
+
note_count: notes.length,
|
|
1443
|
+
skipped: false,
|
|
1444
|
+
},
|
|
1445
|
+
warnings,
|
|
1446
|
+
};
|
|
1447
|
+
}
|
|
1448
|
+
// ═══════════════════════════════════════
|
|
1449
|
+
// Hippocampus Integration (TASK_618)
|
|
1450
|
+
// ═══════════════════════════════════════
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* Call hippocampus_episode_recall via MCP HTTP API (port 18080).
|
|
1454
|
+
* Graceful degradation: returns null if Hippocampus MCP is unavailable.
|
|
1455
|
+
* @param {string} taskId - TASK_XXX
|
|
1456
|
+
* @param {string|null} [agentRole] - Optional agent_role filter (e.g. 'ansuz', 'amelia').
|
|
1457
|
+
* When provided, only episodes for that specialist are returned.
|
|
1458
|
+
* When null, all episodes returned (backwards compatible — legacy episodes have agent_role='pm').
|
|
1459
|
+
* @returns {{ episodes: object[], formatted: string } | null}
|
|
1460
|
+
*/
|
|
1461
|
+
function recallEpisodicMemory(taskId, agentRole = null) {
|
|
1462
|
+
// With task_id: recall episodes for that specific task
|
|
1463
|
+
// Without task_id (or "none"): recall the most recent 1 episode (new Ansuz session context)
|
|
1464
|
+
const hasTask = taskId && taskId !== 'none';
|
|
1465
|
+
const recallArgs = hasTask
|
|
1466
|
+
? { task_id: taskId, limit: 5 }
|
|
1467
|
+
: { limit: 3 };
|
|
1468
|
+
if (agentRole) recallArgs.agent_role = agentRole;
|
|
1469
|
+
// TASK_1983 (R4): explicit project — without this, the call silently follows
|
|
1470
|
+
// the Dashboard's currently-selected project (feedback_mcp_default_project_drift).
|
|
1471
|
+
recallArgs.projectPath = resolveHandoffProjectRoot();
|
|
1472
|
+
|
|
1473
|
+
const script = `
|
|
1474
|
+
const http = require('http');
|
|
1475
|
+
const payload = JSON.stringify({
|
|
1476
|
+
jsonrpc: '2.0', id: 1,
|
|
1477
|
+
method: 'tools/call',
|
|
1478
|
+
params: { name: 'hippocampus_episode_recall', arguments: ${JSON.stringify(recallArgs)} }
|
|
1479
|
+
});
|
|
1480
|
+
const req = http.request({
|
|
1481
|
+
hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
|
|
1482
|
+
method: 'POST',
|
|
1483
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
1484
|
+
timeout: 5000
|
|
1485
|
+
}, (res) => {
|
|
1486
|
+
let d = '';
|
|
1487
|
+
res.on('data', c => d += c);
|
|
1488
|
+
res.on('end', () => process.stdout.write(d));
|
|
1489
|
+
});
|
|
1490
|
+
req.on('error', () => process.exit(0));
|
|
1491
|
+
req.on('timeout', () => { req.destroy(); process.exit(0); });
|
|
1492
|
+
req.write(payload);
|
|
1493
|
+
req.end();
|
|
1494
|
+
`;
|
|
1495
|
+
|
|
1496
|
+
try {
|
|
1497
|
+
const stdout = execFileSync(process.execPath, ['-e', script], {
|
|
1498
|
+
timeout: 8000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
1499
|
+
});
|
|
1500
|
+
if (!stdout || !stdout.trim()) return null;
|
|
1501
|
+
|
|
1502
|
+
const result = JSON.parse(stdout);
|
|
1503
|
+
if (!result || result.error) return null;
|
|
1504
|
+
|
|
1505
|
+
const content = result.result?.content;
|
|
1506
|
+
if (!content || !Array.isArray(content)) return null;
|
|
1507
|
+
|
|
1508
|
+
const textPart = content.find(c => c.type === 'text');
|
|
1509
|
+
if (!textPart || !textPart.text) return null;
|
|
1510
|
+
|
|
1511
|
+
const data = JSON.parse(textPart.text);
|
|
1512
|
+
const episodes = data.episodes || [];
|
|
1513
|
+
if (episodes.length === 0) return null;
|
|
1514
|
+
|
|
1515
|
+
// Format episodes as ## Episodic Memory section
|
|
1516
|
+
const lines = ['## Episodic Memory (from Hippocampus)', ''];
|
|
1517
|
+
const context = hasTask ? taskId : 'recent session';
|
|
1518
|
+
lines.push(`Found ${episodes.length} episode(s) for ${context}:`);
|
|
1519
|
+
lines.push('');
|
|
1520
|
+
|
|
1521
|
+
for (const ep of episodes) {
|
|
1522
|
+
const epData = ep.episode || ep; // hippocampus returns { episode: {...}, entries: [...] }
|
|
1523
|
+
lines.push(`### Episode: ${epData.task_id || epData.id || 'unknown'}`);
|
|
1524
|
+
if (epData.summary) lines.push(`Summary: ${epData.summary}`);
|
|
1525
|
+
if (epData.lesson) lines.push(`Lesson: ${epData.lesson}`);
|
|
1526
|
+
lines.push('');
|
|
1527
|
+
}
|
|
1528
|
+
lines.push('(Use hippocampus_episode_recall for full entry detail if needed)')
|
|
1529
|
+
|
|
1530
|
+
return { episodes, formatted: lines.join('\n') };
|
|
1531
|
+
} catch {
|
|
1532
|
+
return null; // graceful degradation — Hippocampus MCP unavailable
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
function parseIsoDate(value) {
|
|
1537
|
+
if (typeof value !== 'string' || !value.trim()) return null;
|
|
1538
|
+
const date = new Date(value);
|
|
1539
|
+
return Number.isNaN(date.getTime()) ? null : date;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function countStatMatches(raw, pattern) {
|
|
1543
|
+
const match = String(raw || '').match(pattern);
|
|
1544
|
+
return match ? Number.parseInt(match[1], 10) || 0 : 0;
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
function parseGitDiffStat(raw) {
|
|
1548
|
+
const text = String(raw || '').trim();
|
|
1549
|
+
return {
|
|
1550
|
+
files_changed: countStatMatches(text, /(\d+)\s+files?\s+changed/),
|
|
1551
|
+
insertions: countStatMatches(text, /(\d+)\s+insertions?\(\+\)/),
|
|
1552
|
+
deletions: countStatMatches(text, /(\d+)\s+deletions?\(-\)/),
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
function readLastAnsuzSessionClosedAt(projectPath, warnings) {
|
|
1557
|
+
const recallArgs = {
|
|
1558
|
+
limit: 10,
|
|
1559
|
+
projectPath,
|
|
1560
|
+
agent_role: 'ansuz',
|
|
1561
|
+
outcome: 'closed',
|
|
1562
|
+
sort: 'closed_at_desc',
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
const script = `
|
|
1566
|
+
const http = require('http');
|
|
1567
|
+
const payload = JSON.stringify({
|
|
1568
|
+
jsonrpc: '2.0', id: 1,
|
|
1569
|
+
method: 'tools/call',
|
|
1570
|
+
params: { name: 'hippocampus_episode_recall', arguments: ${JSON.stringify(recallArgs)} }
|
|
1571
|
+
});
|
|
1572
|
+
const req = http.request({
|
|
1573
|
+
hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
|
|
1574
|
+
method: 'POST',
|
|
1575
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
1576
|
+
timeout: 5000
|
|
1577
|
+
}, (res) => {
|
|
1578
|
+
let d = '';
|
|
1579
|
+
res.on('data', c => d += c);
|
|
1580
|
+
res.on('end', () => process.stdout.write(d));
|
|
1581
|
+
});
|
|
1582
|
+
req.on('error', () => process.exit(0));
|
|
1583
|
+
req.on('timeout', () => { req.destroy(); process.exit(0); });
|
|
1584
|
+
req.write(payload);
|
|
1585
|
+
req.end();
|
|
1586
|
+
`;
|
|
1587
|
+
|
|
1588
|
+
try {
|
|
1589
|
+
const stdout = execFileSync(process.execPath, ['-e', script], {
|
|
1590
|
+
timeout: 8000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
1591
|
+
});
|
|
1592
|
+
if (!stdout || !stdout.trim()) return null;
|
|
1593
|
+
|
|
1594
|
+
const result = JSON.parse(stdout);
|
|
1595
|
+
if (!result || result.error) return null;
|
|
1596
|
+
|
|
1597
|
+
const content = result.result?.content;
|
|
1598
|
+
if (!content || !Array.isArray(content)) return null;
|
|
1599
|
+
|
|
1600
|
+
const textPart = content.find(c => c.type === 'text');
|
|
1601
|
+
if (!textPart || !textPart.text) return null;
|
|
1602
|
+
|
|
1603
|
+
const data = JSON.parse(textPart.text);
|
|
1604
|
+
const episodes = Array.isArray(data.episodes) ? data.episodes : [];
|
|
1605
|
+
if (episodes.length === 0) return null;
|
|
1606
|
+
|
|
1607
|
+
const sorted = episodes
|
|
1608
|
+
.map((episode) => episode && typeof episode === 'object' ? (episode.episode || episode) : null)
|
|
1609
|
+
.filter(Boolean)
|
|
1610
|
+
.filter((episode) => {
|
|
1611
|
+
const role = String(episode.agent_role || '').toLowerCase();
|
|
1612
|
+
const outcome = String(episode.outcome || '').toLowerCase();
|
|
1613
|
+
return role === 'ansuz' && outcome !== '' && outcome !== 'open';
|
|
1614
|
+
})
|
|
1615
|
+
.map((episode) => ({ episode, closedAt: parseIsoDate(episode.closed_at) }))
|
|
1616
|
+
.filter((item) => item.closedAt !== null)
|
|
1617
|
+
.sort((a, b) => b.closedAt.getTime() - a.closedAt.getTime());
|
|
1618
|
+
|
|
1619
|
+
if (sorted.length === 0) return null;
|
|
1620
|
+
return sorted[0].closedAt.toISOString();
|
|
1621
|
+
} catch (error) {
|
|
1622
|
+
if (Array.isArray(warnings)) {
|
|
1623
|
+
warnings.push(`git_context: hippocampus_episode_recall unavailable, using fallback_24h (${error.message})`);
|
|
1624
|
+
}
|
|
1625
|
+
return null;
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
function resolveGitContextSince(projectPath, warnings) {
|
|
1630
|
+
const now = new Date();
|
|
1631
|
+
const fallback = new Date(now.getTime() - (24 * 60 * 60 * 1000));
|
|
1632
|
+
const lastClosedAtIso = readLastAnsuzSessionClosedAt(projectPath, warnings);
|
|
1633
|
+
const lastClosedAt = parseIsoDate(lastClosedAtIso);
|
|
1634
|
+
|
|
1635
|
+
if (!lastClosedAt) {
|
|
1636
|
+
return {
|
|
1637
|
+
since_iso: fallback.toISOString(),
|
|
1638
|
+
since_source: 'fallback_24h',
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
return {
|
|
1643
|
+
since_iso: lastClosedAt.toISOString(),
|
|
1644
|
+
since_source: 'last_session',
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
function loadGitContext(projectPath, since_iso) {
|
|
1649
|
+
if (!projectPath || !since_iso) return null;
|
|
1650
|
+
if (!fs.existsSync(path.join(projectPath, '.git'))) return null;
|
|
1651
|
+
|
|
1652
|
+
try {
|
|
1653
|
+
const logStdout = execFileSync(
|
|
1654
|
+
'git',
|
|
1655
|
+
['log', '--oneline', `--since=${since_iso}`, '-n', '10', '--pretty=format:%h\t%s\t%aI'],
|
|
1656
|
+
{ cwd: projectPath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }
|
|
1657
|
+
);
|
|
1658
|
+
const diffStdout = execFileSync(
|
|
1659
|
+
'git',
|
|
1660
|
+
['diff', '--stat', 'HEAD'],
|
|
1661
|
+
{ cwd: projectPath, encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true }
|
|
1662
|
+
);
|
|
1663
|
+
|
|
1664
|
+
const recent_commits = String(logStdout || '')
|
|
1665
|
+
.split(/\r?\n/)
|
|
1666
|
+
.map((line) => line.trim())
|
|
1667
|
+
.filter(Boolean)
|
|
1668
|
+
.map((line) => {
|
|
1669
|
+
const [hash, subject, author_time] = line.split('\t');
|
|
1670
|
+
if (!hash || !subject || !author_time) return null;
|
|
1671
|
+
return { hash, subject, author_time };
|
|
1672
|
+
})
|
|
1673
|
+
.filter(Boolean);
|
|
1674
|
+
|
|
1675
|
+
return {
|
|
1676
|
+
recent_commits,
|
|
1677
|
+
wt_diff_stat: parseGitDiffStat(diffStdout),
|
|
1678
|
+
since_iso,
|
|
1679
|
+
since_source: 'unknown',
|
|
1680
|
+
};
|
|
1681
|
+
} catch {
|
|
1682
|
+
return null;
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function readGitContext(projectPath, warnings) {
|
|
1687
|
+
const projectYaml = readProjectYaml();
|
|
1688
|
+
if (isPhase0GitContextDisabled(projectYaml)) return null;
|
|
1689
|
+
if (!fs.existsSync(path.join(projectPath, '.git'))) return null;
|
|
1690
|
+
|
|
1691
|
+
const since = resolveGitContextSince(projectPath, warnings);
|
|
1692
|
+
const gitContext = loadGitContext(projectPath, since.since_iso);
|
|
1693
|
+
if (!gitContext) return null;
|
|
1694
|
+
|
|
1695
|
+
gitContext.since_source = since.since_source;
|
|
1696
|
+
return gitContext;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// ═══════════════════════════════════════
|
|
1700
|
+
// User Model — Language + Profile Injection (TASK_1041)
|
|
1701
|
+
// ═══════════════════════════════════════
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* Detect preferred language from OS locale. Falls back to 'en' when no signal.
|
|
1705
|
+
* Priority: LANG → LC_ALL → Intl.DateTimeFormat resolvedOptions().locale.
|
|
1706
|
+
* Maps zh-prefix/Hans/Hant to 'zh'; everything else to 'en'.
|
|
1707
|
+
* @returns {string} 'zh' | 'en'
|
|
1708
|
+
*/
|
|
1709
|
+
function detectOSLocale() {
|
|
1710
|
+
const candidates = [];
|
|
1711
|
+
if (process.env.LANG) candidates.push(String(process.env.LANG));
|
|
1712
|
+
if (process.env.LC_ALL) candidates.push(String(process.env.LC_ALL));
|
|
1713
|
+
try {
|
|
1714
|
+
const locale = Intl.DateTimeFormat().resolvedOptions().locale;
|
|
1715
|
+
if (locale) candidates.push(String(locale));
|
|
1716
|
+
} catch {
|
|
1717
|
+
// Intl unavailable (should not happen on Node >= 13) — skip.
|
|
1718
|
+
}
|
|
1719
|
+
for (const c of candidates) {
|
|
1720
|
+
if (/zh|Hans|Hant/i.test(c)) return 'zh';
|
|
1721
|
+
}
|
|
1722
|
+
return 'en';
|
|
1723
|
+
}
|
|
1724
|
+
|
|
1725
|
+
/**
|
|
1726
|
+
* Read user_model.json from .kenaz/ and derive a compact summary for Phase 0 injection.
|
|
1727
|
+
* On missing file: returns {loaded:false, preferred_language:<os-locale>, source:'os_locale'}.
|
|
1728
|
+
* On JSON parse failure: pushes a warning, returns {loaded:false, preferred_language:<os-locale>, source:'os_locale'}.
|
|
1729
|
+
* On success: returns {loaded:true, preferred_language:<file>, source:'file', schema_version, last_updated, model}.
|
|
1730
|
+
* Source values: 'file' | 'os_locale' | 'default'.
|
|
1731
|
+
* @param {string} basePath - Project root (typically BASE_DIR).
|
|
1732
|
+
* @param {string[]} warnings - Mutable warnings collector from enterPhase.
|
|
1733
|
+
* @returns {object}
|
|
1734
|
+
*/
|
|
1735
|
+
function readUserModel(basePath, warnings) {
|
|
1736
|
+
const filePath = path.join(basePath, '.kenaz', 'user_model.json');
|
|
1737
|
+
if (!fs.existsSync(filePath)) {
|
|
1738
|
+
return {
|
|
1739
|
+
loaded: false,
|
|
1740
|
+
preferred_language: detectOSLocale(),
|
|
1741
|
+
source: 'os_locale',
|
|
1742
|
+
schema_version: null,
|
|
1743
|
+
last_updated: null,
|
|
1744
|
+
model: null,
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
try {
|
|
1748
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
1749
|
+
const parsed = JSON.parse(raw);
|
|
1750
|
+
const lang = (parsed && parsed.communication && typeof parsed.communication.preferred_language === 'string')
|
|
1751
|
+
? parsed.communication.preferred_language
|
|
1752
|
+
: null;
|
|
1753
|
+
if (!lang) {
|
|
1754
|
+
return {
|
|
1755
|
+
loaded: true,
|
|
1756
|
+
preferred_language: detectOSLocale(),
|
|
1757
|
+
source: 'os_locale',
|
|
1758
|
+
schema_version: parsed.schema_version || null,
|
|
1759
|
+
last_updated: parsed.last_updated || null,
|
|
1760
|
+
model: parsed,
|
|
1761
|
+
};
|
|
1762
|
+
}
|
|
1763
|
+
return {
|
|
1764
|
+
loaded: true,
|
|
1765
|
+
preferred_language: lang,
|
|
1766
|
+
source: 'file',
|
|
1767
|
+
schema_version: parsed.schema_version || null,
|
|
1768
|
+
last_updated: parsed.last_updated || null,
|
|
1769
|
+
model: parsed,
|
|
1770
|
+
};
|
|
1771
|
+
} catch (e) {
|
|
1772
|
+
if (Array.isArray(warnings)) {
|
|
1773
|
+
warnings.push(`USER_MODEL: .kenaz/user_model.json parse failed (${e.message}) — falling back to OS locale`);
|
|
1774
|
+
}
|
|
1775
|
+
return {
|
|
1776
|
+
loaded: false,
|
|
1777
|
+
preferred_language: detectOSLocale(),
|
|
1778
|
+
source: 'os_locale',
|
|
1779
|
+
schema_version: null,
|
|
1780
|
+
last_updated: null,
|
|
1781
|
+
model: null,
|
|
1782
|
+
};
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
function getUserProfilePath() {
|
|
1787
|
+
return path.join(os.homedir(), '.kenaz', 'user_profile.json');
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
function readUserProfile(warnings) {
|
|
1791
|
+
const filePath = getUserProfilePath();
|
|
1792
|
+
if (!fs.existsSync(filePath)) return null;
|
|
1793
|
+
try {
|
|
1794
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1795
|
+
} catch (e) {
|
|
1796
|
+
if (Array.isArray(warnings)) {
|
|
1797
|
+
warnings.push(`USER_PROFILE: ~/.kenaz/user_profile.json parse failed (${e.message})`);
|
|
1798
|
+
}
|
|
1799
|
+
return null;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
function readUserObservations(days = 30, warnings) {
|
|
1804
|
+
if (!fs.existsSync(USER_OBSERVATIONS_PATH)) {
|
|
1805
|
+
return { loaded: false, observations: [] };
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
1809
|
+
try {
|
|
1810
|
+
const raw = fs.readFileSync(USER_OBSERVATIONS_PATH, 'utf8');
|
|
1811
|
+
const observations = raw
|
|
1812
|
+
.split(/\r?\n/)
|
|
1813
|
+
.filter(Boolean)
|
|
1814
|
+
.map((line) => {
|
|
1815
|
+
try { return JSON.parse(line); } catch { return null; }
|
|
1816
|
+
})
|
|
1817
|
+
.filter(Boolean)
|
|
1818
|
+
.filter((entry) => {
|
|
1819
|
+
const ts = new Date(entry.timestamp).getTime();
|
|
1820
|
+
return Number.isFinite(ts) && ts >= cutoff;
|
|
1821
|
+
});
|
|
1822
|
+
return { loaded: true, observations };
|
|
1823
|
+
} catch (e) {
|
|
1824
|
+
if (Array.isArray(warnings)) {
|
|
1825
|
+
warnings.push(`USER_OBSERVATIONS: read failed (${e.message})`);
|
|
1826
|
+
}
|
|
1827
|
+
return { loaded: false, observations: [] };
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
function computeSignalRateTrend(recentCount, previousCount, recentDays, previousDays) {
|
|
1832
|
+
const recentRate = recentDays > 0 ? recentCount / recentDays : 0;
|
|
1833
|
+
const previousRate = previousDays > 0 ? previousCount / previousDays : 0;
|
|
1834
|
+
if (recentCount === 0 && previousCount === 0) return { trend: 'stable', recentRate, previousRate };
|
|
1835
|
+
if (recentRate >= previousRate * 1.35 + 0.01) return { trend: 'increasing', recentRate, previousRate };
|
|
1836
|
+
if (previousRate >= recentRate * 1.35 + 0.01) return { trend: 'decreasing', recentRate, previousRate };
|
|
1837
|
+
return { trend: 'stable', recentRate, previousRate };
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function summarizeCommunicationWindow(entries) {
|
|
1841
|
+
const summary = {
|
|
1842
|
+
messageCount: 0,
|
|
1843
|
+
totalChars: 0,
|
|
1844
|
+
langCounts: { zh: 0, en: 0, mixed: 0 },
|
|
1845
|
+
avgLength: 0,
|
|
1846
|
+
dominantLanguage: null,
|
|
1847
|
+
};
|
|
1848
|
+
|
|
1849
|
+
for (const entry of entries) {
|
|
1850
|
+
if (entry.signal_type !== 'communication_meta' || !entry.batch) continue;
|
|
1851
|
+
const messages = entry.batch.messages || 0;
|
|
1852
|
+
const totalChars = entry.batch.total_chars || 0;
|
|
1853
|
+
summary.messageCount += messages;
|
|
1854
|
+
summary.totalChars += totalChars;
|
|
1855
|
+
for (const lang of ['zh', 'en', 'mixed']) {
|
|
1856
|
+
summary.langCounts[lang] += (entry.batch.lang_counts && entry.batch.lang_counts[lang]) || 0;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
if (summary.messageCount > 0) {
|
|
1861
|
+
summary.avgLength = Math.round(summary.totalChars / summary.messageCount);
|
|
1862
|
+
const dominant = Object.entries(summary.langCounts).sort((a, b) => b[1] - a[1])[0];
|
|
1863
|
+
summary.dominantLanguage = dominant && dominant[1] > 0 ? dominant[0] : null;
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
return summary;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
function ratioDelta(lang, recentSummary, previousSummary) {
|
|
1870
|
+
const recentRatio = recentSummary.messageCount > 0
|
|
1871
|
+
? recentSummary.langCounts[lang] / recentSummary.messageCount
|
|
1872
|
+
: 0;
|
|
1873
|
+
const previousRatio = previousSummary.messageCount > 0
|
|
1874
|
+
? previousSummary.langCounts[lang] / previousSummary.messageCount
|
|
1875
|
+
: 0;
|
|
1876
|
+
return {
|
|
1877
|
+
recent: recentRatio,
|
|
1878
|
+
previous: previousRatio,
|
|
1879
|
+
delta: recentRatio - previousRatio,
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
function buildDynamicUserContext(warnings) {
|
|
1884
|
+
const windowDays = 30;
|
|
1885
|
+
const recentDays = 7;
|
|
1886
|
+
const previousDays = windowDays - recentDays;
|
|
1887
|
+
const observationResult = readUserObservations(windowDays, warnings);
|
|
1888
|
+
const userProfile = readUserProfile(warnings);
|
|
1889
|
+
|
|
1890
|
+
if (!observationResult.loaded || observationResult.observations.length === 0) {
|
|
1891
|
+
return {
|
|
1892
|
+
loaded: observationResult.loaded,
|
|
1893
|
+
observation_count: 0,
|
|
1894
|
+
window_days: windowDays,
|
|
1895
|
+
significant_change_detected: false,
|
|
1896
|
+
insight_lines: [],
|
|
1897
|
+
summary: null,
|
|
1898
|
+
correction_trend: { trend: 'stable', recent_count: 0, previous_count: 0, recent_rate: 0, previous_rate: 0 },
|
|
1899
|
+
domain_shift: { new_domains: [], recent_domains: [], known_domains: userProfile?.expertise ? Object.keys(userProfile.expertise) : [] },
|
|
1900
|
+
communication_shift: {
|
|
1901
|
+
language_ratio_shift: { changed: false, previous_dominant: null, recent_dominant: null, deltas: { zh: 0, en: 0, mixed: 0 } },
|
|
1902
|
+
message_length_trend: { trend: 'stable', recent_avg: 0, previous_avg: 0 },
|
|
1903
|
+
},
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
const splitTime = Date.now() - recentDays * 24 * 60 * 60 * 1000;
|
|
1908
|
+
const recent = [];
|
|
1909
|
+
const previous = [];
|
|
1910
|
+
for (const entry of observationResult.observations) {
|
|
1911
|
+
const ts = new Date(entry.timestamp).getTime();
|
|
1912
|
+
if (!Number.isFinite(ts)) continue;
|
|
1913
|
+
if (ts >= splitTime) recent.push(entry);
|
|
1914
|
+
else previous.push(entry);
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const recentCorrections = recent.filter((e) => e.signal_type === 'correction').length;
|
|
1918
|
+
const previousCorrections = previous.filter((e) => e.signal_type === 'correction').length;
|
|
1919
|
+
const correctionRates = computeSignalRateTrend(recentCorrections, previousCorrections, recentDays, previousDays);
|
|
1920
|
+
|
|
1921
|
+
const recentDomains = [...new Set(recent.map((e) => e.domain).filter(Boolean))].sort();
|
|
1922
|
+
const previousDomains = new Set(previous.map((e) => e.domain).filter(Boolean));
|
|
1923
|
+
const knownDomains = new Set(Object.keys(userProfile?.expertise || {}));
|
|
1924
|
+
const newDomains = recentDomains.filter((domain) => !previousDomains.has(domain) && !knownDomains.has(domain));
|
|
1925
|
+
|
|
1926
|
+
const recentComm = summarizeCommunicationWindow(recent);
|
|
1927
|
+
const previousComm = summarizeCommunicationWindow(previous);
|
|
1928
|
+
const languageDeltas = {
|
|
1929
|
+
zh: ratioDelta('zh', recentComm, previousComm),
|
|
1930
|
+
en: ratioDelta('en', recentComm, previousComm),
|
|
1931
|
+
mixed: ratioDelta('mixed', recentComm, previousComm),
|
|
1932
|
+
};
|
|
1933
|
+
const languageShiftChanged =
|
|
1934
|
+
recentComm.dominantLanguage !== previousComm.dominantLanguage ||
|
|
1935
|
+
Object.values(languageDeltas).some((d) => Math.abs(d.delta) >= 0.2);
|
|
1936
|
+
|
|
1937
|
+
const lengthTrend = (() => {
|
|
1938
|
+
if (recentComm.messageCount === 0 || previousComm.messageCount === 0) {
|
|
1939
|
+
return { trend: 'stable', recent_avg: recentComm.avgLength, previous_avg: previousComm.avgLength };
|
|
1940
|
+
}
|
|
1941
|
+
if (recentComm.avgLength >= previousComm.avgLength * 1.15 + 20) {
|
|
1942
|
+
return { trend: 'longer', recent_avg: recentComm.avgLength, previous_avg: previousComm.avgLength };
|
|
1943
|
+
}
|
|
1944
|
+
if (previousComm.avgLength >= recentComm.avgLength * 1.15 + 20) {
|
|
1945
|
+
return { trend: 'shorter', recent_avg: recentComm.avgLength, previous_avg: previousComm.avgLength };
|
|
1946
|
+
}
|
|
1947
|
+
return { trend: 'stable', recent_avg: recentComm.avgLength, previous_avg: previousComm.avgLength };
|
|
1948
|
+
})();
|
|
1949
|
+
|
|
1950
|
+
const insightLines = [];
|
|
1951
|
+
if (correctionRates.trend === 'increasing' && recentCorrections > 0) {
|
|
1952
|
+
insightLines.push(`User corrections are increasing this week (${recentCorrections} recent vs ${previousCorrections} earlier in the month).`);
|
|
1953
|
+
} else if (correctionRates.trend === 'decreasing' && previousCorrections > 0) {
|
|
1954
|
+
insightLines.push(`User corrections are easing this week (${recentCorrections} recent vs ${previousCorrections} earlier in the month).`);
|
|
1955
|
+
}
|
|
1956
|
+
if (newDomains.length > 0) {
|
|
1957
|
+
insightLines.push(`New user focus domains appeared recently: ${newDomains.slice(0, 3).join(', ')}.`);
|
|
1958
|
+
} else if (languageShiftChanged) {
|
|
1959
|
+
insightLines.push(`Communication style shifted recently (${previousComm.dominantLanguage || 'none'} → ${recentComm.dominantLanguage || 'none'}).`);
|
|
1960
|
+
} else if (lengthTrend.trend !== 'stable') {
|
|
1961
|
+
insightLines.push(`Recent user messages are trending ${lengthTrend.trend} (${lengthTrend.previous_avg} → ${lengthTrend.recent_avg} chars avg).`);
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
return {
|
|
1965
|
+
loaded: true,
|
|
1966
|
+
observation_count: observationResult.observations.length,
|
|
1967
|
+
window_days: windowDays,
|
|
1968
|
+
significant_change_detected: insightLines.length > 0,
|
|
1969
|
+
insight_lines: insightLines.slice(0, 2),
|
|
1970
|
+
summary: insightLines.slice(0, 2).join(' '),
|
|
1971
|
+
correction_trend: {
|
|
1972
|
+
trend: correctionRates.trend,
|
|
1973
|
+
recent_count: recentCorrections,
|
|
1974
|
+
previous_count: previousCorrections,
|
|
1975
|
+
recent_rate: Number(correctionRates.recentRate.toFixed(3)),
|
|
1976
|
+
previous_rate: Number(correctionRates.previousRate.toFixed(3)),
|
|
1977
|
+
},
|
|
1978
|
+
domain_shift: {
|
|
1979
|
+
new_domains: newDomains,
|
|
1980
|
+
recent_domains: recentDomains,
|
|
1981
|
+
known_domains: [...knownDomains].sort(),
|
|
1982
|
+
},
|
|
1983
|
+
communication_shift: {
|
|
1984
|
+
language_ratio_shift: {
|
|
1985
|
+
changed: languageShiftChanged,
|
|
1986
|
+
previous_dominant: previousComm.dominantLanguage,
|
|
1987
|
+
recent_dominant: recentComm.dominantLanguage,
|
|
1988
|
+
deltas: {
|
|
1989
|
+
zh: Number(languageDeltas.zh.delta.toFixed(3)),
|
|
1990
|
+
en: Number(languageDeltas.en.delta.toFixed(3)),
|
|
1991
|
+
mixed: Number(languageDeltas.mixed.delta.toFixed(3)),
|
|
1992
|
+
},
|
|
1993
|
+
},
|
|
1994
|
+
message_length_trend: lengthTrend,
|
|
1995
|
+
},
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
/**
|
|
2000
|
+
* Call an MCP tool through the local Dashboard HTTP bridge.
|
|
2001
|
+
* Returns parsed tool text payload as JSON, or null on any failure.
|
|
2002
|
+
* Graceful degradation is intentional for Phase 0 boot.
|
|
2003
|
+
* @param {string} toolName
|
|
2004
|
+
* @param {object} args
|
|
2005
|
+
* @returns {object|null}
|
|
2006
|
+
*/
|
|
2007
|
+
function callMcpTool(toolName, args = {}) {
|
|
2008
|
+
const script = `
|
|
2009
|
+
const http = require('http');
|
|
2010
|
+
const fs = require('fs');
|
|
2011
|
+
const input = JSON.parse(fs.readFileSync(0, 'utf8'));
|
|
2012
|
+
const payload = JSON.stringify({
|
|
2013
|
+
jsonrpc: '2.0', id: 1,
|
|
2014
|
+
method: 'tools/call',
|
|
2015
|
+
params: { name: input.toolName, arguments: input.args }
|
|
2016
|
+
});
|
|
2017
|
+
const req = http.request({
|
|
2018
|
+
hostname: '127.0.0.1', port: 18080, path: '/api/mcp',
|
|
2019
|
+
method: 'POST',
|
|
2020
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
2021
|
+
timeout: 5000
|
|
2022
|
+
}, (res) => {
|
|
2023
|
+
let d = '';
|
|
2024
|
+
res.on('data', c => d += c);
|
|
2025
|
+
res.on('end', () => process.stdout.write(d));
|
|
2026
|
+
});
|
|
2027
|
+
req.on('error', () => process.exit(0));
|
|
2028
|
+
req.on('timeout', () => { req.destroy(); process.exit(0); });
|
|
2029
|
+
req.write(payload);
|
|
2030
|
+
req.end();
|
|
2031
|
+
`;
|
|
2032
|
+
|
|
2033
|
+
try {
|
|
2034
|
+
const stdout = execFileSync(process.execPath, ['-e', script], {
|
|
2035
|
+
input: JSON.stringify({ toolName, args }),
|
|
2036
|
+
timeout: 8000, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
2037
|
+
});
|
|
2038
|
+
if (!stdout || !stdout.trim()) return null;
|
|
2039
|
+
|
|
2040
|
+
const result = JSON.parse(stdout);
|
|
2041
|
+
if (!result || result.error) return null;
|
|
2042
|
+
|
|
2043
|
+
const content = result.result?.content;
|
|
2044
|
+
if (!content || !Array.isArray(content)) return null;
|
|
2045
|
+
|
|
2046
|
+
const textPart = content.find(c => c.type === 'text');
|
|
2047
|
+
if (!textPart || !textPart.text) return null;
|
|
2048
|
+
|
|
2049
|
+
return JSON.parse(textPart.text);
|
|
2050
|
+
} catch {
|
|
2051
|
+
return null;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
/**
|
|
2056
|
+
* Auto-restore active session handoff during Phase 0.
|
|
2057
|
+
* Reads active slot, archives it to Hippocampus, then deletes the slot.
|
|
2058
|
+
* Returns a compact summary for Phase 0 banner rendering.
|
|
2059
|
+
* Graceful degradation: on archive failure, keep the slot for the next session.
|
|
2060
|
+
* @param {string[]} warnings
|
|
2061
|
+
* @returns {{loaded:boolean, summary?:object|null, retained?:boolean}}
|
|
2062
|
+
*/
|
|
2063
|
+
function restoreSessionHandoff(warnings) {
|
|
2064
|
+
// TASK_1829: this is a LEGACY-slot back-compat consume/cleanup path only.
|
|
2065
|
+
// The singular slot was retired by M3 (TASK_1760) and normally does not exist —
|
|
2066
|
+
// this function then no-ops. Live handoffs live in per-session slots
|
|
2067
|
+
// (.kenaz/specialist/handoffs/) and are read via the ansuz_handoff_snapshot MCP
|
|
2068
|
+
// tool (kenaz.rs, TASK_1828) / handoff-resolver; per-session slots are NOT
|
|
2069
|
+
// consumed-and-deleted here (deleting the latest slot would race parallel sessions).
|
|
2070
|
+
const handoffPath = ANSUZ_HANDOFF_PATH;
|
|
2071
|
+
|
|
2072
|
+
if (!fs.existsSync(handoffPath)) {
|
|
2073
|
+
return { loaded: false, summary: null };
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
let handoff;
|
|
2077
|
+
try {
|
|
2078
|
+
handoff = JSON.parse(fs.readFileSync(handoffPath, 'utf8'));
|
|
2079
|
+
} catch (e) {
|
|
2080
|
+
warnings.push(`HANDOFF: invalid legacy handoff slot (${e.message}) — deleting corrupted legacy file`);
|
|
2081
|
+
try { fs.unlinkSync(handoffPath); } catch (unlinkErr) {
|
|
2082
|
+
warnings.push(`HANDOFF: failed to delete corrupted active slot (${unlinkErr.message})`);
|
|
2083
|
+
}
|
|
2084
|
+
return { loaded: false, summary: null };
|
|
2085
|
+
}
|
|
2086
|
+
|
|
2087
|
+
if (handoff && handoff.cleared === true) {
|
|
2088
|
+
return { loaded: false, summary: null };
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
const summary = {
|
|
2092
|
+
session_id: handoff?.session_id || null,
|
|
2093
|
+
topic: handoff?.isbar?.situation?.current_topic || null,
|
|
2094
|
+
first_action: handoff?.isbar?.recommendation?.first_action || null,
|
|
2095
|
+
pending_decisions: Array.isArray(handoff?.isbar?.situation?.pending_decisions)
|
|
2096
|
+
? handoff.isbar.situation.pending_decisions
|
|
2097
|
+
: [],
|
|
2098
|
+
unfinished: Array.isArray(handoff?.isbar?.assessment?.unfinished)
|
|
2099
|
+
? handoff.isbar.assessment.unfinished
|
|
2100
|
+
: [],
|
|
2101
|
+
};
|
|
2102
|
+
|
|
2103
|
+
const taskId = handoff?.session_id || `ansuz-handoff-${Date.now()}`;
|
|
2104
|
+
const scope = `session-handoff:${taskId}`;
|
|
2105
|
+
const serialized = JSON.stringify(handoff, null, 2);
|
|
2106
|
+
|
|
2107
|
+
// TASK_1983 (R4): explicit project on every Hippocampus call — omitted projectPath
|
|
2108
|
+
// silently follows the Dashboard's currently-selected project (feedback_mcp_default_project_drift).
|
|
2109
|
+
const handoffProjectPath = resolveHandoffProjectRoot();
|
|
2110
|
+
|
|
2111
|
+
const started = callMcpTool('hippocampus_episode_start', {
|
|
2112
|
+
task_id: taskId,
|
|
2113
|
+
scope,
|
|
2114
|
+
agent_role: 'ansuz',
|
|
2115
|
+
agent_skill: 'session_handoff',
|
|
2116
|
+
projectPath: handoffProjectPath,
|
|
2117
|
+
});
|
|
2118
|
+
const episodeId = started && started.episode_id;
|
|
2119
|
+
if (!episodeId) {
|
|
2120
|
+
warnings.push('HANDOFF: failed to archive to Hippocampus (episode_start unavailable) — retaining file, summary still surfaced');
|
|
2121
|
+
return { loaded: true, summary, retained: true, archive_failed: true };
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
const appended = callMcpTool('hippocampus_episode_append', {
|
|
2125
|
+
episode_id: episodeId,
|
|
2126
|
+
phase: 'handoff',
|
|
2127
|
+
content: serialized,
|
|
2128
|
+
outcome: 'restored',
|
|
2129
|
+
projectPath: handoffProjectPath,
|
|
2130
|
+
});
|
|
2131
|
+
if (!appended || !appended.entry_id) {
|
|
2132
|
+
warnings.push('HANDOFF: failed to archive to Hippocampus (episode_append unavailable) — retaining file, summary still surfaced');
|
|
2133
|
+
return { loaded: true, summary, retained: true, archive_failed: true };
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
const closed = callMcpTool('hippocampus_episode_close', {
|
|
2137
|
+
episode_id: episodeId,
|
|
2138
|
+
summary: `Session handoff from ${taskId}: topic=${summary.topic || 'unknown'}, pending=${summary.pending_decisions.length} decisions`,
|
|
2139
|
+
lesson: summary.first_action || 'Resume the preserved session context before new analysis.',
|
|
2140
|
+
outcome: 'success',
|
|
2141
|
+
projectPath: handoffProjectPath,
|
|
2142
|
+
});
|
|
2143
|
+
if (!closed || closed.status !== 'closed') {
|
|
2144
|
+
warnings.push('HANDOFF: failed to archive to Hippocampus (episode_close unavailable) — retaining file, summary still surfaced');
|
|
2145
|
+
return { loaded: true, summary, retained: true, archive_failed: true };
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
try {
|
|
2149
|
+
fs.unlinkSync(handoffPath);
|
|
2150
|
+
} catch (e) {
|
|
2151
|
+
warnings.push(`HANDOFF: archived to Hippocampus but failed to delete active slot (${e.message})`);
|
|
2152
|
+
return { loaded: true, summary, retained: true };
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
return { loaded: true, summary };
|
|
2156
|
+
}
|
|
2157
|
+
|
|
2158
|
+
// ═══════════════════════════════════════
|
|
2159
|
+
// Blind Spot Guard Injection (TASK_923)
|
|
2160
|
+
// Re-injects active blind_spot guards at enterPhase for phases >= 3
|
|
2161
|
+
// to prevent context compression from losing Phase 0 self-model awareness.
|
|
2162
|
+
// ═══════════════════════════════════════
|
|
2163
|
+
|
|
2164
|
+
/**
|
|
2165
|
+
* Parse the guard mapping YAML (simple subset — no external deps).
|
|
2166
|
+
* Returns { guards: { 'B-XXX': { description, phases, conditions: { N: text } } } }
|
|
2167
|
+
*/
|
|
2168
|
+
function parseBlindSpotGuardsYaml(content) {
|
|
2169
|
+
const guards = {};
|
|
2170
|
+
let currentId = null;
|
|
2171
|
+
let inConditions = false;
|
|
2172
|
+
|
|
2173
|
+
for (const line of content.split('\n')) {
|
|
2174
|
+
// Skip comments and empty lines
|
|
2175
|
+
if (/^\s*#/.test(line) || !line.trim()) continue;
|
|
2176
|
+
|
|
2177
|
+
// Top-level guard ID: " B-001:"
|
|
2178
|
+
const idMatch = line.match(/^\s{2}(B-\d+):\s*$/);
|
|
2179
|
+
if (idMatch) {
|
|
2180
|
+
currentId = idMatch[1];
|
|
2181
|
+
guards[currentId] = { description: '', phases: [], conditions: {} };
|
|
2182
|
+
inConditions = false;
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
if (!currentId) continue;
|
|
2187
|
+
|
|
2188
|
+
// description: "..."
|
|
2189
|
+
const descMatch = line.match(/^\s+description:\s*"(.+)"\s*$/);
|
|
2190
|
+
if (descMatch) {
|
|
2191
|
+
guards[currentId].description = descMatch[1];
|
|
2192
|
+
inConditions = false;
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
// phases: [3, 8, 9]
|
|
2197
|
+
const phasesMatch = line.match(/^\s+phases:\s*\[([^\]]*)\]/);
|
|
2198
|
+
if (phasesMatch) {
|
|
2199
|
+
guards[currentId].phases = phasesMatch[1].split(',').map(s => parseInt(s.trim(), 10)).filter(n => !isNaN(n));
|
|
2200
|
+
inConditions = false;
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
// conditions:
|
|
2205
|
+
if (/^\s+conditions:\s*$/.test(line)) {
|
|
2206
|
+
inConditions = true;
|
|
2207
|
+
continue;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
// condition entry: " 3: "text""
|
|
2211
|
+
if (inConditions) {
|
|
2212
|
+
const condMatch = line.match(/^\s+(\d+):\s*"(.+)"\s*$/);
|
|
2213
|
+
if (condMatch) {
|
|
2214
|
+
guards[currentId].conditions[parseInt(condMatch[1], 10)] = condMatch[2];
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
return { guards };
|
|
2220
|
+
}
|
|
2221
|
+
|
|
2222
|
+
/**
|
|
2223
|
+
* Read active blind spot guards for a given phase and specialist.
|
|
2224
|
+
* Merges self-model blind_spots (active only) with guard mapping YAML.
|
|
2225
|
+
* Returns [] on any failure (graceful degradation).
|
|
2226
|
+
*
|
|
2227
|
+
* @param {number} phase - Current phase number
|
|
2228
|
+
* @param {string} specialistName - Specialist name (e.g. 'Ansuz')
|
|
2229
|
+
* @returns {Array<{ id: string, description: string, guard: string }>}
|
|
2230
|
+
*/
|
|
2231
|
+
function readBlindSpotGuards(phase, specialistName) {
|
|
2232
|
+
const specialist = specialistName || 'Ansuz';
|
|
2233
|
+
|
|
2234
|
+
// 1. Read merged self-model to get blind_spots with status
|
|
2235
|
+
const growthPath = path.join(BASE_DIR, '.kenaz', 'specialist', `${specialist.toLowerCase()}_self_model.json`);
|
|
2236
|
+
const seedPath = path.join(BASE_DIR, '.specialist', specialist, 'self_model_seed.json');
|
|
2237
|
+
|
|
2238
|
+
let blindSpots = [];
|
|
2239
|
+
try {
|
|
2240
|
+
// Try growth model first (has status field)
|
|
2241
|
+
if (fs.existsSync(growthPath)) {
|
|
2242
|
+
const growth = JSON.parse(fs.readFileSync(growthPath, 'utf8'));
|
|
2243
|
+
blindSpots = growth.blind_spots || [];
|
|
2244
|
+
}
|
|
2245
|
+
// Merge with seed core_blind_spots (always active by definition)
|
|
2246
|
+
if (fs.existsSync(seedPath)) {
|
|
2247
|
+
const seed = JSON.parse(fs.readFileSync(seedPath, 'utf8'));
|
|
2248
|
+
const coreSpots = (seed.core_blind_spots || []).map(s => ({ ...s, status: 'active' }));
|
|
2249
|
+
// Merge: growth entries override seed entries by ID
|
|
2250
|
+
const growthIds = new Set(blindSpots.map(b => b.id));
|
|
2251
|
+
for (const core of coreSpots) {
|
|
2252
|
+
if (!growthIds.has(core.id)) blindSpots.push(core);
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
} catch {
|
|
2256
|
+
return []; // graceful degradation
|
|
2257
|
+
}
|
|
2258
|
+
|
|
2259
|
+
// Filter to active only
|
|
2260
|
+
const activeSpots = blindSpots.filter(b => b.status && b.status.startsWith('active'));
|
|
2261
|
+
if (activeSpots.length === 0) return [];
|
|
2262
|
+
|
|
2263
|
+
// 2. Read guard mapping YAML
|
|
2264
|
+
const guardYamlPath = path.join(BASE_DIR, '.specialist', specialist, 'guards', 'blind_spot_guards.yaml');
|
|
2265
|
+
let guardMapping = {};
|
|
2266
|
+
try {
|
|
2267
|
+
if (!fs.existsSync(guardYamlPath)) return [];
|
|
2268
|
+
const yamlContent = fs.readFileSync(guardYamlPath, 'utf8');
|
|
2269
|
+
const parsed = parseBlindSpotGuardsYaml(yamlContent);
|
|
2270
|
+
guardMapping = parsed.guards || {};
|
|
2271
|
+
} catch {
|
|
2272
|
+
return []; // graceful degradation
|
|
2273
|
+
}
|
|
2274
|
+
|
|
2275
|
+
// 3. Map active blind spots to phase-specific guards
|
|
2276
|
+
const guards = [];
|
|
2277
|
+
for (const spot of activeSpots) {
|
|
2278
|
+
const mapping = guardMapping[spot.id];
|
|
2279
|
+
if (!mapping) continue;
|
|
2280
|
+
if (!mapping.phases.includes(phase)) continue;
|
|
2281
|
+
const condition = mapping.conditions[phase];
|
|
2282
|
+
if (!condition) continue;
|
|
2283
|
+
|
|
2284
|
+
guards.push({
|
|
2285
|
+
id: spot.id,
|
|
2286
|
+
description: spot.description,
|
|
2287
|
+
guard: condition,
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
return guards;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
// ═══════════════════════════════════════
|
|
2295
|
+
// Self-Model Seed Protection (TASK_869)
|
|
2296
|
+
// ═══════════════════════════════════════
|
|
2297
|
+
|
|
2298
|
+
/**
|
|
2299
|
+
* Compute SHA256 of a file's raw content.
|
|
2300
|
+
*/
|
|
2301
|
+
function computeFileSHA256(filePath) {
|
|
2302
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
2303
|
+
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
/**
|
|
2307
|
+
* Verify self_model_seed.json integrity against stored checksum.
|
|
2308
|
+
* On first run (no .sha256 file), generates checksum + backup.
|
|
2309
|
+
* Returns { ok, expected, actual, first_run?, warning? }
|
|
2310
|
+
*/
|
|
2311
|
+
function verifySeedIntegrity() {
|
|
2312
|
+
if (!fs.existsSync(SEED_PATH)) {
|
|
2313
|
+
return { ok: false, expected: null, actual: null, warning: 'self_model_seed.json not found' };
|
|
2314
|
+
}
|
|
2315
|
+
if (!fs.existsSync(SEED_CHECKSUM_PATH)) {
|
|
2316
|
+
// First run — generate reference checksum and backup
|
|
2317
|
+
const checksum = computeFileSHA256(SEED_PATH);
|
|
2318
|
+
fs.writeFileSync(SEED_CHECKSUM_PATH, checksum, 'utf8');
|
|
2319
|
+
fs.copyFileSync(SEED_PATH, SEED_BACKUP_PATH);
|
|
2320
|
+
return { ok: true, expected: checksum, actual: checksum, first_run: true };
|
|
2321
|
+
}
|
|
2322
|
+
const expected = fs.readFileSync(SEED_CHECKSUM_PATH, 'utf8').trim();
|
|
2323
|
+
const actual = computeFileSHA256(SEED_PATH);
|
|
2324
|
+
return { ok: expected === actual, expected, actual };
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
/**
|
|
2328
|
+
* Restore self_model_seed.json from backup and reset checksum.
|
|
2329
|
+
* Returns { restored: boolean, error?: string }
|
|
2330
|
+
*/
|
|
2331
|
+
function restoreSeedFromBackup() {
|
|
2332
|
+
if (!fs.existsSync(SEED_BACKUP_PATH)) {
|
|
2333
|
+
return { restored: false, error: 'No backup file found at self_model_seed.backup.json' };
|
|
2334
|
+
}
|
|
2335
|
+
try {
|
|
2336
|
+
fs.copyFileSync(SEED_BACKUP_PATH, SEED_PATH);
|
|
2337
|
+
const restoredChecksum = computeFileSHA256(SEED_PATH);
|
|
2338
|
+
fs.writeFileSync(SEED_CHECKSUM_PATH, restoredChecksum, 'utf8');
|
|
2339
|
+
return { restored: true };
|
|
2340
|
+
} catch (e) {
|
|
2341
|
+
return { restored: false, error: e.message };
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
/**
|
|
2346
|
+
* Merge seed layer + growth layer into a unified self-model.
|
|
2347
|
+
* Seed fields ALWAYS win (especially narrative).
|
|
2348
|
+
* Growth-only fields (tendencies, blind_spots, growth_edges) come from growth layer.
|
|
2349
|
+
* Returns { model, seed, growth, warnings[] }
|
|
2350
|
+
*/
|
|
2351
|
+
function mergeSeedAndGrowth() {
|
|
2352
|
+
const warnings = [];
|
|
2353
|
+
let seed = null;
|
|
2354
|
+
try {
|
|
2355
|
+
seed = JSON.parse(fs.readFileSync(SEED_PATH, 'utf8'));
|
|
2356
|
+
} catch (e) {
|
|
2357
|
+
return { model: null, seed: null, growth: null, warnings: [`Seed read error: ${e.message}`] };
|
|
2358
|
+
}
|
|
2359
|
+
|
|
2360
|
+
let growth = null;
|
|
2361
|
+
if (fs.existsSync(GROWTH_PATH)) {
|
|
2362
|
+
try {
|
|
2363
|
+
growth = JSON.parse(fs.readFileSync(GROWTH_PATH, 'utf8'));
|
|
2364
|
+
} catch (e) {
|
|
2365
|
+
warnings.push(`Growth layer read error: ${e.message}`);
|
|
2366
|
+
}
|
|
2367
|
+
}
|
|
2368
|
+
|
|
2369
|
+
// Seed fields always win
|
|
2370
|
+
const model = {
|
|
2371
|
+
version: seed.version,
|
|
2372
|
+
specialist: seed.specialist,
|
|
2373
|
+
narrative: seed.narrative, // seed wins — immutable soul anchor
|
|
2374
|
+
core_tendencies: seed.core_tendencies || [],
|
|
2375
|
+
core_blind_spots: seed.core_blind_spots || [],
|
|
2376
|
+
};
|
|
2377
|
+
|
|
2378
|
+
// Growth fields layered on top (observable, accumulating evidence)
|
|
2379
|
+
if (growth) {
|
|
2380
|
+
model.tendencies = growth.tendencies || [];
|
|
2381
|
+
model.blind_spots = growth.blind_spots || [];
|
|
2382
|
+
model.growth_edges = growth.growth_edges || [];
|
|
2383
|
+
model.last_updated = growth.last_updated || null;
|
|
2384
|
+
model.last_updated_by = growth.last_updated_by || null;
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
return { model, seed, growth, warnings };
|
|
2388
|
+
}
|
|
2389
|
+
|
|
2390
|
+
/** Growth-only fields allowed for Phase 11 self-model writes (seed-protected fields excluded) */
|
|
2391
|
+
const GROWTH_ONLY_FIELDS = ['tendencies', 'blind_spots', 'growth_edges', 'last_updated', 'last_updated_by'];
|
|
2392
|
+
|
|
2393
|
+
/**
|
|
2394
|
+
* Write growth-only fields to .kenaz/ansuz_self_model.json.
|
|
2395
|
+
* Strips any seed-protected fields (narrative, core_*) from the update.
|
|
2396
|
+
* Returns { written: boolean, error?: string }
|
|
2397
|
+
*/
|
|
2398
|
+
function writeSelfModelGrowth(update) {
|
|
2399
|
+
let current = {};
|
|
2400
|
+
if (fs.existsSync(GROWTH_PATH)) {
|
|
2401
|
+
try { current = JSON.parse(fs.readFileSync(GROWTH_PATH, 'utf8')); } catch { /* start fresh */ }
|
|
2402
|
+
}
|
|
2403
|
+
const cleaned = {};
|
|
2404
|
+
for (const field of GROWTH_ONLY_FIELDS) {
|
|
2405
|
+
if (field in update) cleaned[field] = update[field];
|
|
2406
|
+
}
|
|
2407
|
+
const updated = {
|
|
2408
|
+
...current,
|
|
2409
|
+
...cleaned,
|
|
2410
|
+
last_updated: cleaned.last_updated || new Date().toISOString(),
|
|
2411
|
+
};
|
|
2412
|
+
// Preserve metadata fields that live in the growth file
|
|
2413
|
+
updated.version = current.version || '1.0.0';
|
|
2414
|
+
updated.specialist = current.specialist || 'ansuz';
|
|
2415
|
+
try {
|
|
2416
|
+
const kenazDir = path.dirname(GROWTH_PATH);
|
|
2417
|
+
if (!fs.existsSync(kenazDir)) fs.mkdirSync(kenazDir, { recursive: true });
|
|
2418
|
+
fs.writeFileSync(GROWTH_PATH, JSON.stringify(updated, null, 2), 'utf8');
|
|
2419
|
+
return { written: true };
|
|
2420
|
+
} catch (e) {
|
|
2421
|
+
return { written: false, error: e.message };
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
// ═══════════════════════════════════════
|
|
2426
|
+
// Ansuz Inbox — Fatigue Warning Messages (TASK_943)
|
|
2427
|
+
// ═══════════════════════════════════════
|
|
2428
|
+
|
|
2429
|
+
const ANSUZ_INBOX_PATH = path.join(BASE_DIR, '.kenaz', 'specialist', 'ansuz_inbox.json');
|
|
2430
|
+
|
|
2431
|
+
/**
|
|
2432
|
+
* Read unprocessed fatigue_warning messages from ansuz_inbox.json.
|
|
2433
|
+
* Returns array of messages (empty if none or file absent).
|
|
2434
|
+
* Graceful degradation: returns [] on any error.
|
|
2435
|
+
*/
|
|
2436
|
+
function readAnsuzInboxMessages() {
|
|
2437
|
+
try {
|
|
2438
|
+
if (!fs.existsSync(ANSUZ_INBOX_PATH)) return [];
|
|
2439
|
+
const raw = fs.readFileSync(ANSUZ_INBOX_PATH, 'utf8');
|
|
2440
|
+
const data = JSON.parse(raw);
|
|
2441
|
+
const messages = Array.isArray(data.messages) ? data.messages : [];
|
|
2442
|
+
const processedIds = new Set(Array.isArray(data.processed_ids) ? data.processed_ids : []);
|
|
2443
|
+
return messages.filter(m => m.type === 'fatigue_warning' && !processedIds.has(m.id));
|
|
2444
|
+
} catch {
|
|
2445
|
+
return [];
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2449
|
+
// ═══════════════════════════════════════
|
|
2450
|
+
// Enter Logic
|
|
2451
|
+
// ═══════════════════════════════════════
|
|
2452
|
+
|
|
2453
|
+
function enterPhase(phase, taskId, specialistName = null) {
|
|
2454
|
+
const chart = readChart(taskId);
|
|
2455
|
+
const warnings = [];
|
|
2456
|
+
|
|
2457
|
+
// Prerequisite: previous phase must be completed (phase > 0)
|
|
2458
|
+
if (phase > 0) {
|
|
2459
|
+
const prevKey = String(phase - 1);
|
|
2460
|
+
const prev = chart.phases[prevKey];
|
|
2461
|
+
if (prev && prev.status !== 'completed') {
|
|
2462
|
+
return outputResult({
|
|
2463
|
+
phase,
|
|
2464
|
+
action: 'enter',
|
|
2465
|
+
modules_loaded: [],
|
|
2466
|
+
module_content: {},
|
|
2467
|
+
checklist: [],
|
|
2468
|
+
warnings: [],
|
|
2469
|
+
blocked: true,
|
|
2470
|
+
block_reason: `Phase ${prevKey} is not completed (status: ${prev.status}). Complete the previous phase before entering phase ${phase}.`,
|
|
2471
|
+
});
|
|
2472
|
+
}
|
|
2473
|
+
if (!prev) {
|
|
2474
|
+
warnings.push(`No record for phase ${prevKey} — treating as first entry to phase ${phase}`);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// TASK_1104: Tool reflex enforcement at enter time.
|
|
2479
|
+
// These run BEFORE module/instruction loading so blocked phases abort cheaply.
|
|
2480
|
+
// No emergency override — ritual failures must be fixed by the ritual, not bypassed.
|
|
2481
|
+
|
|
2482
|
+
// Phase 0: consecutive ritual skip escalation. >=3 trailing skips in
|
|
2483
|
+
// ansuz_ritual_log.jsonl force a hard block until the agent acks and the
|
|
2484
|
+
// log is advanced by a non-skip entry.
|
|
2485
|
+
if (phase === 0) {
|
|
2486
|
+
// TASK_1113: warn (non-blocking) when Phase 0 is entered without the
|
|
2487
|
+
// gated session.js prompt having been consumed. We detect this by
|
|
2488
|
+
// inspecting ritual_state.json for a `manifest_read_at` marker — the
|
|
2489
|
+
// gated boot path leaves this unset until the agent explicitly records
|
|
2490
|
+
// having read the manifest. Missing marker → 'manifest_skipped' entry
|
|
2491
|
+
// in ansuz_ritual_log.jsonl.
|
|
2492
|
+
try {
|
|
2493
|
+
const RITUAL_STATE_PATH = path.join(BASE_DIR, '.kenaz', 'specialist', 'ritual_state.json');
|
|
2494
|
+
if (fs.existsSync(RITUAL_STATE_PATH)) {
|
|
2495
|
+
const rs = JSON.parse(fs.readFileSync(RITUAL_STATE_PATH, 'utf8'));
|
|
2496
|
+
if (!rs || !rs.manifest_read_at) {
|
|
2497
|
+
warnings.push('Phase 0 manifest_skipped: ritual_state.json has no manifest_read_at — gated boot manifest appears to have been bypassed. Run `node .specialist/tools/session.js prompt Ansuz --depth=kernel --gated` and read the manifest before phase-gate enter.');
|
|
2498
|
+
appendRitualLog('manifest', 'skipped', { task_id: taskId, phase, reason: 'no manifest_read_at in ritual_state.json' });
|
|
2499
|
+
}
|
|
2500
|
+
} else {
|
|
2501
|
+
warnings.push('Phase 0 manifest_skipped: ritual_state.json missing — gated boot not invoked. Run session.js prompt Ansuz --gated first.');
|
|
2502
|
+
appendRitualLog('manifest', 'skipped', { task_id: taskId, phase, reason: 'ritual_state.json missing' });
|
|
2503
|
+
}
|
|
2504
|
+
} catch (e) {
|
|
2505
|
+
warnings.push(`Phase 0 manifest check error: ${e.message}`);
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
const skips = countConsecutiveRitualSkips();
|
|
2509
|
+
if (skips >= 3) {
|
|
2510
|
+
return outputResult({
|
|
2511
|
+
phase,
|
|
2512
|
+
action: 'enter',
|
|
2513
|
+
modules_loaded: [],
|
|
2514
|
+
module_content: {},
|
|
2515
|
+
checklist: [],
|
|
2516
|
+
warnings: [],
|
|
2517
|
+
blocked: true,
|
|
2518
|
+
block_reason: `Phase 0 HARD-BLOCKED: ${skips} consecutive ritual skips detected in ansuz_ritual_log.jsonl. Agent must acknowledge: ritual failures fix the ritual — no emergency override. Append a non-skip entry (outcome=completed or low_quality with reason) to ${RITUAL_LOG_PATH} before re-entering Phase 0.`,
|
|
2519
|
+
});
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
|
|
2523
|
+
// Phase 9: ddx check. If the task description contains uncertainty markers,
|
|
2524
|
+
// ddx_log.jsonl MUST contain a matching entry.
|
|
2525
|
+
if (phase === 9) {
|
|
2526
|
+
const desc = readTaskDescription(taskId);
|
|
2527
|
+
if (desc && /\b(uncertain|unclear|hypothesis|maybe|possibly)\b/i.test(desc)) {
|
|
2528
|
+
if (!ddxLogHas(taskId)) {
|
|
2529
|
+
appendRitualLog('ddx', 'skipped', { task_id: taskId, phase });
|
|
2530
|
+
return outputResult({
|
|
2531
|
+
phase,
|
|
2532
|
+
action: 'enter',
|
|
2533
|
+
modules_loaded: [],
|
|
2534
|
+
module_content: {},
|
|
2535
|
+
checklist: [],
|
|
2536
|
+
warnings: [],
|
|
2537
|
+
blocked: true,
|
|
2538
|
+
block_reason: `Phase 9 BLOCKED: task description contains uncertainty markers (uncertain|unclear|hypothesis|maybe|possibly) but no ddx_log entry for ${taskId}. Enumerate competing hypotheses and append to ${DDX_LOG_PATH} (fields: {task_id, hypotheses:[...], timestamp}) before entering Phase 9.`,
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2543
|
+
|
|
2544
|
+
// Phase 11: inscribe check. If chart / progress notes contain learning markers,
|
|
2545
|
+
// inscribe_log.jsonl MUST contain a synapse_inscribe record for the task.
|
|
2546
|
+
if (phase === 11) {
|
|
2547
|
+
let chartText = '';
|
|
2548
|
+
try { chartText = JSON.stringify(readChart(taskId)); } catch { /* ignore */ }
|
|
2549
|
+
if (chartText && /\b(learned|insight|surprise|non-obvious)\b/i.test(chartText)) {
|
|
2550
|
+
if (!inscribeLogHas(taskId)) {
|
|
2551
|
+
appendRitualLog('inscribe', 'skipped', { task_id: taskId, phase });
|
|
2552
|
+
return outputResult({
|
|
2553
|
+
phase,
|
|
2554
|
+
action: 'enter',
|
|
2555
|
+
modules_loaded: [],
|
|
2556
|
+
module_content: {},
|
|
2557
|
+
checklist: [],
|
|
2558
|
+
warnings: [],
|
|
2559
|
+
blocked: true,
|
|
2560
|
+
block_reason: `Phase 11 BLOCKED: session contains learning markers (learned|insight|surprise|non-obvious) but no synapse_inscribe record for ${taskId}. Call synapse_inscribe and append an entry to ${INSCRIBE_LOG_PATH} (fields: {task_id, rune_id, timestamp}) before entering Phase 11.`,
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
}
|
|
2565
|
+
|
|
2566
|
+
// Load phase instructions and context hints (TASK_583)
|
|
2567
|
+
const phaseInstructions = loadPhaseInstructions(phase);
|
|
2568
|
+
if (phaseInstructions.warning) {
|
|
2569
|
+
warnings.push(phaseInstructions.warning);
|
|
2570
|
+
}
|
|
2571
|
+
const contextHints = buildContextHints(phase, chart, phaseInstructions, warnings);
|
|
2572
|
+
|
|
2573
|
+
// Load inject rules/specialist files declared in phase YAML (TASK_609)
|
|
2574
|
+
let injectedRules = {};
|
|
2575
|
+
if (phaseInstructions.content) {
|
|
2576
|
+
const injectConfig = extractInjectConfig(phaseInstructions.content);
|
|
2577
|
+
injectedRules = loadInjectedFiles(injectConfig, warnings);
|
|
2578
|
+
}
|
|
2579
|
+
|
|
2580
|
+
// Decide and load modules
|
|
2581
|
+
const moduleNames = decideModules(phase, chart);
|
|
2582
|
+
const moduleContent = {};
|
|
2583
|
+
const loadedModules = [];
|
|
2584
|
+
|
|
2585
|
+
for (const name of moduleNames) {
|
|
2586
|
+
const mod = loadModule(name);
|
|
2587
|
+
moduleContent[name] = mod;
|
|
2588
|
+
if (mod.error) {
|
|
2589
|
+
warnings.push(mod.error);
|
|
2590
|
+
} else {
|
|
2591
|
+
loadedModules.push(name);
|
|
2592
|
+
if (!chart.vitals.modules_ever_loaded.includes(name)) {
|
|
2593
|
+
chart.vitals.modules_ever_loaded.push(name);
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
if (mod.warning) {
|
|
2597
|
+
warnings.push(mod.warning);
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
// Record phase entry
|
|
2602
|
+
const phaseKey = String(phase);
|
|
2603
|
+
const existingPhase = chart.phases[phaseKey];
|
|
2604
|
+
if (existingPhase) {
|
|
2605
|
+
warnings.push(`Re-entering phase ${phase} (previous status: ${existingPhase.status})`);
|
|
2606
|
+
existingPhase.entered_at = new Date().toISOString();
|
|
2607
|
+
existingPhase.status = 'active';
|
|
2608
|
+
existingPhase.modules_loaded = loadedModules;
|
|
2609
|
+
existingPhase.checklist_result = null;
|
|
2610
|
+
// Reset checklist_items to base list so lesson guard conditions are not duplicated
|
|
2611
|
+
// on re-entry (they will be re-appended below when guards are loaded).
|
|
2612
|
+
existingPhase.checklist_items = PHASE_CHECKLISTS[phase] ? [...PHASE_CHECKLISTS[phase]] : [];
|
|
2613
|
+
} else {
|
|
2614
|
+
chart.phases[phaseKey] = {
|
|
2615
|
+
entered_at: new Date().toISOString(),
|
|
2616
|
+
exited_at: null,
|
|
2617
|
+
status: 'active',
|
|
2618
|
+
modules_loaded: loadedModules,
|
|
2619
|
+
checklist_items: PHASE_CHECKLISTS[phase] || [],
|
|
2620
|
+
checklist_result: null,
|
|
2621
|
+
data: {},
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
// Phase 0: hippocampus recall removed from phase-gate.
|
|
2626
|
+
// Ansuz calls hippocampus_episode_recall directly when episodic context is needed.
|
|
2627
|
+
// This keeps phase-gate Phase 0 output small (injected_rules like agent-workflow.yaml
|
|
2628
|
+
// are 20KB+ and are already part of the assembled Ansuz persona).
|
|
2629
|
+
|
|
2630
|
+
// Phase 0: seed integrity check + merge (TASK_869)
|
|
2631
|
+
let selfModelResult = null;
|
|
2632
|
+
if (phase === 0) {
|
|
2633
|
+
// 1. Verify seed integrity; restore from backup on tamper detection
|
|
2634
|
+
let integrityInfo;
|
|
2635
|
+
try {
|
|
2636
|
+
integrityInfo = verifySeedIntegrity();
|
|
2637
|
+
} catch (e) {
|
|
2638
|
+
integrityInfo = { ok: false, expected: null, actual: null, warning: `Integrity check error: ${e.message}` };
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
if (!integrityInfo.ok) {
|
|
2642
|
+
if (integrityInfo.warning) {
|
|
2643
|
+
warnings.push(`SEED: ${integrityInfo.warning}`);
|
|
2644
|
+
} else {
|
|
2645
|
+
// Checksum mismatch — attempt restore
|
|
2646
|
+
const restore = restoreSeedFromBackup();
|
|
2647
|
+
if (restore.restored) {
|
|
2648
|
+
warnings.push('SECURITY: self_model_seed.json checksum mismatch — restored from backup');
|
|
2649
|
+
} else {
|
|
2650
|
+
warnings.push(
|
|
2651
|
+
`SECURITY: self_model_seed.json tampered (expected: ${(integrityInfo.expected || '').slice(0, 8)}..., ` +
|
|
2652
|
+
`actual: ${(integrityInfo.actual || '').slice(0, 8)}...) — restore failed: ${restore.error}`
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
} else if (integrityInfo.first_run) {
|
|
2657
|
+
warnings.push('SEED: first-run — checksum generated and backup created (self_model_seed.sha256 + .backup.json)');
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
// 2. Merge seed + growth into unified self-model
|
|
2661
|
+
const { model, warnings: mergeWarnings } = mergeSeedAndGrowth();
|
|
2662
|
+
if (mergeWarnings && mergeWarnings.length > 0) warnings.push(...mergeWarnings);
|
|
2663
|
+
|
|
2664
|
+
selfModelResult = {
|
|
2665
|
+
loaded: model !== null,
|
|
2666
|
+
integrity_ok: integrityInfo.ok,
|
|
2667
|
+
model: model || null,
|
|
2668
|
+
};
|
|
2669
|
+
|
|
2670
|
+
// Track self_model_loaded in phase data for exit validation reference
|
|
2671
|
+
const phaseRec = chart.phases[String(phase)];
|
|
2672
|
+
if (phaseRec) phaseRec.data.self_model_loaded = model !== null;
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
// Phase 0: User model language injection (TASK_1041)
|
|
2676
|
+
// Load .kenaz/user_model.json to inject preferred_language; fall back to OS locale.
|
|
2677
|
+
let userModelResult = null;
|
|
2678
|
+
if (phase === 0) {
|
|
2679
|
+
userModelResult = readUserModel(BASE_DIR, warnings);
|
|
2680
|
+
// Track in phase data for downstream reference
|
|
2681
|
+
const phaseRec = chart.phases[String(phase)];
|
|
2682
|
+
if (phaseRec) {
|
|
2683
|
+
phaseRec.data.user_model_loaded = userModelResult.loaded;
|
|
2684
|
+
phaseRec.data.user_model_language = userModelResult.preferred_language;
|
|
2685
|
+
phaseRec.data.user_model_source = userModelResult.source;
|
|
2686
|
+
}
|
|
2687
|
+
}
|
|
2688
|
+
|
|
2689
|
+
// Phase 0: Session handoff auto-restore (Tier 3x)
|
|
2690
|
+
// Reduce Ansuz boot cognitive load by restoring archived context in phase-gate itself.
|
|
2691
|
+
let sessionHandoffResult = null;
|
|
2692
|
+
if (phase === 0) {
|
|
2693
|
+
sessionHandoffResult = restoreSessionHandoff(warnings);
|
|
2694
|
+
const phaseRec = chart.phases[String(phase)];
|
|
2695
|
+
if (phaseRec) {
|
|
2696
|
+
phaseRec.data.session_handoff_loaded = sessionHandoffResult.loaded;
|
|
2697
|
+
if (sessionHandoffResult.summary) {
|
|
2698
|
+
phaseRec.data.session_handoff_summary = sessionHandoffResult.summary;
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
// Phase 0: Dynamic user context synthesis (TASK_1218)
|
|
2704
|
+
// Read recent user observations and cross-reference the long-lived user profile.
|
|
2705
|
+
let dynamicUserContext = null;
|
|
2706
|
+
if (phase === 0) {
|
|
2707
|
+
dynamicUserContext = buildDynamicUserContext(warnings);
|
|
2708
|
+
const phaseRec = chart.phases[String(phase)];
|
|
2709
|
+
if (phaseRec) {
|
|
2710
|
+
phaseRec.data.dynamic_user_context = dynamicUserContext;
|
|
2711
|
+
}
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
// Phase 0: Git context auto-enrichment
|
|
2715
|
+
let gitContextResult = null;
|
|
2716
|
+
if (phase === 0) {
|
|
2717
|
+
gitContextResult = readGitContext(process.cwd(), warnings);
|
|
2718
|
+
const phaseRec = chart.phases[String(phase)];
|
|
2719
|
+
if (phaseRec) {
|
|
2720
|
+
phaseRec.data.git_context = gitContextResult;
|
|
2721
|
+
phaseRec.data.git_context_loaded = gitContextResult !== null;
|
|
2722
|
+
phaseRec.data.git_context_since_iso = gitContextResult ? gitContextResult.since_iso : null;
|
|
2723
|
+
phaseRec.data.git_context_since_source = gitContextResult ? gitContextResult.since_source : null;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
// Phase 8: compression fallback only (the Muninn dispatch modulation was retired, TASK_2345).
|
|
2728
|
+
if (phase === 8 && taskId) {
|
|
2729
|
+
emitCompressionFallbackIfNeeded(chart, taskId, warnings);
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2732
|
+
// Phases >= 3: Blind Spot Guard Injection (TASK_923)
|
|
2733
|
+
// Re-inject active blind_spot guards that were loaded in Phase 0 self-model
|
|
2734
|
+
// but lost to context compression by later phases.
|
|
2735
|
+
let blindSpotGuards = null;
|
|
2736
|
+
if (phase >= 3) {
|
|
2737
|
+
const guards = readBlindSpotGuards(phase, specialistName);
|
|
2738
|
+
if (guards.length > 0) {
|
|
2739
|
+
blindSpotGuards = guards;
|
|
2740
|
+
// Record in phase data
|
|
2741
|
+
const phaseRec = chart.phases[String(phase)];
|
|
2742
|
+
if (phaseRec) {
|
|
2743
|
+
phaseRec.data.blind_spot_guards_injected = guards.map(g => g.id);
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
// Phases >= 1: Lesson Guard Injection (TASK_1071)
|
|
2749
|
+
// Inject durable reflections promoted via the Reflection-to-Control pipeline.
|
|
2750
|
+
// Unlike blind_spot_guards (self-model B-XXX), lesson guards (L-XXX) are promoted
|
|
2751
|
+
// from Synapse runes, Muninn episode lessons, or manual reflection promotion.
|
|
2752
|
+
let lessonGuards = null;
|
|
2753
|
+
if (phase >= 1) {
|
|
2754
|
+
try {
|
|
2755
|
+
const lessonGuardModule = require(path.join(BASE_DIR, '.specialist', 'tools', 'lesson-guard.js'));
|
|
2756
|
+
const lgList = lessonGuardModule.readLessonGuardsForPhase(phase);
|
|
2757
|
+
if (lgList.length > 0) {
|
|
2758
|
+
// TASK_1164: Run precompute for guards with precompute field BEFORE building checklist
|
|
2759
|
+
// so rendered output can embed inline below the [LG:L-XXX] line.
|
|
2760
|
+
// runPrecompute mutates each guard object with a precompute_result field.
|
|
2761
|
+
// Wrapped in try/catch so an unexpected error never blocks lesson_guards loading.
|
|
2762
|
+
try {
|
|
2763
|
+
const { runPrecompute } = require(path.join(BASE_DIR, '.specialist', 'tools', 'precompute-runner.js'));
|
|
2764
|
+
const phaseRecForData = chart.phases[String(phase)];
|
|
2765
|
+
const taskData = (phaseRecForData && phaseRecForData.data) ? phaseRecForData.data : {};
|
|
2766
|
+
runPrecompute(lgList, taskData, BASE_DIR);
|
|
2767
|
+
} catch (pcErr) {
|
|
2768
|
+
warnings.push(`precompute: ${pcErr.message}`);
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
lessonGuards = lgList;
|
|
2772
|
+
// Record injected guard IDs in phase data for audit trail
|
|
2773
|
+
// Phase behavior change: append guard conditions to checklist_items so
|
|
2774
|
+
// the specialist sees them as explicit obligations at phase entry (TASK_1076).
|
|
2775
|
+
const phaseRec = chart.phases[String(phase)];
|
|
2776
|
+
if (phaseRec) {
|
|
2777
|
+
phaseRec.data.lesson_guards_injected = lgList.map(g => g.id);
|
|
2778
|
+
// TASK_1163: Snapshot full guard state per-injection so post-complete
|
|
2779
|
+
// detector reads the signature that was active AT injection time, not the
|
|
2780
|
+
// current yaml state (which may have been mutated by auto-expire etc).
|
|
2781
|
+
phaseRec.data.lesson_guards_snapshot = lgList.map(g => ({
|
|
2782
|
+
id: g.id,
|
|
2783
|
+
failure_signature: g.failure_signature || null,
|
|
2784
|
+
injection_ts: new Date().toISOString(),
|
|
2785
|
+
phase,
|
|
2786
|
+
}));
|
|
2787
|
+
// TASK_1163: Increment injection_count for each guard (used by hit_rate calc)
|
|
2788
|
+
try {
|
|
2789
|
+
const lgMod = require(path.join(BASE_DIR, '.specialist', 'tools', 'lesson-guard.js'));
|
|
2790
|
+
if (typeof lgMod.incrementInjectionCount === 'function') {
|
|
2791
|
+
for (const g of lgList) lgMod.incrementInjectionCount(g.id);
|
|
2792
|
+
}
|
|
2793
|
+
} catch { /* never block injection on tracking failure */ }
|
|
2794
|
+
for (const lg of lgList) {
|
|
2795
|
+
phaseRec.checklist_items.push(`[LG:${lg.id}] ${lg.guard}`);
|
|
2796
|
+
// TASK_1164: Embed precompute result as ↳ line below the guard text.
|
|
2797
|
+
// Fall back to text-only injection if precompute returned nothing.
|
|
2798
|
+
if (lg.precompute && lg.precompute_result != null) {
|
|
2799
|
+
phaseRec.checklist_items.push(`↳ ${lg.precompute_result}`);
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
} catch { /* graceful degradation — lesson-guard.js unavailable */ }
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
if (warnings.length > 0) {
|
|
2808
|
+
chart.vitals.warnings.push(...warnings);
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2811
|
+
writeChart(chart);
|
|
2812
|
+
|
|
2813
|
+
return outputResult({
|
|
2814
|
+
phase,
|
|
2815
|
+
action: 'enter',
|
|
2816
|
+
modules_loaded: loadedModules,
|
|
2817
|
+
module_content: moduleContent,
|
|
2818
|
+
checklist: (chart.phases[String(phase)] || {}).checklist_items || PHASE_CHECKLISTS[phase] || [],
|
|
2819
|
+
// Phase 0 skips instructions + injected_rules: the assembled persona already contains them.
|
|
2820
|
+
// Other phases still receive full instructions + rules.
|
|
2821
|
+
instructions: phase === 0 ? null : phaseInstructions.content,
|
|
2822
|
+
context_hints: Object.keys(contextHints).length > 0 ? contextHints : null,
|
|
2823
|
+
injected_rules: phase === 0 ? {} : injectedRules,
|
|
2824
|
+
poem_stanza: phase === 0 ? (phaseInstructions.poem || null) : undefined,
|
|
2825
|
+
poem_cli_format: phase === 0 ? (extractBlockScalar(phaseInstructions.content || '', 'cli_format') || null) : undefined,
|
|
2826
|
+
self_model: selfModelResult, // Phase 0 only; null for all other phases
|
|
2827
|
+
user_model: userModelResult, // Phase 0 only; null for all other phases (TASK_1041)
|
|
2828
|
+
dynamic_user_context: phase === 0 ? dynamicUserContext : null,
|
|
2829
|
+
git_context: phase === 0 ? gitContextResult : null,
|
|
2830
|
+
session_handoff: phase === 0 ? sessionHandoffResult : null,
|
|
2831
|
+
blind_spot_guards: blindSpotGuards, // Phases >= 3; null for phases 0-2 (TASK_923)
|
|
2832
|
+
lesson_guards: lessonGuards, // Phases >= 1; null when no active guards (TASK_1071)
|
|
2833
|
+
inbox_messages: (() => { const m = readAnsuzInboxMessages(); return m.length > 0 ? m : null; })(), // TASK_943: fatigue_warning delivery
|
|
2834
|
+
// TASK_1170: exit_template — Phase 0 only. Tells Ansuz exactly what shape the
|
|
2835
|
+
// exit call must have, so the 43% stdout_markers skip rate is preventable at
|
|
2836
|
+
// enter time instead of discovered at exit time after a ~2-minute retry cycle.
|
|
2837
|
+
exit_template: phase === 0 ? {
|
|
2838
|
+
exit_command: 'node .specialist/tools/phase-gate.js exit --phase=0 --data=<json>',
|
|
2839
|
+
exit_required_fields: {
|
|
2840
|
+
poem_displayed: true,
|
|
2841
|
+
ash_loaded: true,
|
|
2842
|
+
session_handoff_loaded: true,
|
|
2843
|
+
consent_uttered: true,
|
|
2844
|
+
// Dynamic read — must equal ANSUZ_CONSENT_STRING exactly; never hard-code a copy
|
|
2845
|
+
consent_string: ANSUZ_CONSENT_STRING,
|
|
2846
|
+
mirror_acknowledged: { id: 'B-XXX', understanding: '<sentence >=30 chars literally containing B-XXX>' },
|
|
2847
|
+
inheritance_grounded: true,
|
|
2848
|
+
// Dynamic read — must equal REQUIRED_STDOUT_MARKERS; never hard-code a copy
|
|
2849
|
+
stdout_markers_emitted: REQUIRED_STDOUT_MARKERS,
|
|
2850
|
+
},
|
|
2851
|
+
exit_verification_notes: [
|
|
2852
|
+
'stdout_markers must appear as user-visible plain text literals in the last 200 lines of conversation jsonl — NOT inside thinking blocks or markdown code fences.',
|
|
2853
|
+
'mirror_acknowledged.understanding must contain the literal string from mirror_acknowledged.id (e.g. "B-XXX").',
|
|
2854
|
+
'consent_string must be byte-for-byte equal to the ANSUZ_CONSENT_STRING constant — no paraphrase, no partial, no extra characters.',
|
|
2855
|
+
],
|
|
2856
|
+
} : null,
|
|
2857
|
+
warnings,
|
|
2858
|
+
blocked: false,
|
|
2859
|
+
block_reason: null,
|
|
2860
|
+
});
|
|
2861
|
+
}
|
|
2862
|
+
|
|
2863
|
+
// ═══════════════════════════════════════
|
|
2864
|
+
// Prediction Recording (TASK_914)
|
|
2865
|
+
// Write expected outcomes at Phase 3 exit for outcome tracking in post-complete.
|
|
2866
|
+
// ═══════════════════════════════════════
|
|
2867
|
+
|
|
2868
|
+
/**
|
|
2869
|
+
* Map an overall_risk or risk string to a confidence value.
|
|
2870
|
+
* Higher risk → lower confidence.
|
|
2871
|
+
*/
|
|
2872
|
+
function riskToConfidence(risk) {
|
|
2873
|
+
if (!risk) return 0.7;
|
|
2874
|
+
const r = String(risk).toLowerCase();
|
|
2875
|
+
if (r === 'high' || r === 'critical') return 0.4;
|
|
2876
|
+
if (r === 'medium') return 0.7;
|
|
2877
|
+
if (r === 'low' || r === 'none') return 0.9;
|
|
2878
|
+
return 0.7;
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
/**
|
|
2882
|
+
* Map risk level to predicted health delta.
|
|
2883
|
+
*/
|
|
2884
|
+
function riskToHealthDelta(risk) {
|
|
2885
|
+
if (!risk) return 'neutral';
|
|
2886
|
+
const r = String(risk).toLowerCase();
|
|
2887
|
+
if (r === 'high' || r === 'critical') return 'negative';
|
|
2888
|
+
if (r === 'low' || r === 'none') return 'positive';
|
|
2889
|
+
return 'neutral';
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
/**
|
|
2893
|
+
* Write prediction objects to ansuz_predictions.json.
|
|
2894
|
+
* Atomic write via tmp file + rename.
|
|
2895
|
+
* @param {Array} tasks - Array of task objects with { task_id, scope_paths, known_risks, confidence, source }
|
|
2896
|
+
* @param {Array} warnings - Mutable warnings array to push messages into
|
|
2897
|
+
*/
|
|
2898
|
+
function writePredictions(tasks, warnings) {
|
|
2899
|
+
if (!tasks || !Array.isArray(tasks) || tasks.length === 0) {
|
|
2900
|
+
warnings.push('Phase 3 prediction: no tasks provided — skipping prediction recording');
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
const dir = path.dirname(PREDICTIONS_PATH);
|
|
2905
|
+
if (!fs.existsSync(dir)) {
|
|
2906
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
// Read existing predictions (atomic read-modify-write)
|
|
2910
|
+
let existing = { predictions: [] };
|
|
2911
|
+
try {
|
|
2912
|
+
if (fs.existsSync(PREDICTIONS_PATH)) {
|
|
2913
|
+
const raw = fs.readFileSync(PREDICTIONS_PATH, 'utf8');
|
|
2914
|
+
const parsed = JSON.parse(raw);
|
|
2915
|
+
if (parsed && Array.isArray(parsed.predictions)) {
|
|
2916
|
+
existing = parsed;
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
} catch (e) {
|
|
2920
|
+
warnings.push(`Phase 3 prediction: failed to read existing predictions — starting fresh: ${e.message}`);
|
|
2921
|
+
}
|
|
2922
|
+
|
|
2923
|
+
const now = new Date().toISOString();
|
|
2924
|
+
|
|
2925
|
+
for (const task of tasks) {
|
|
2926
|
+
const taskId = task.task_id || task.id;
|
|
2927
|
+
if (!taskId) continue;
|
|
2928
|
+
|
|
2929
|
+
// Normalize scope_paths to forward slashes
|
|
2930
|
+
const expectedFiles = (task.scope_paths || []).map(p => String(p).replace(/\\/g, '/'));
|
|
2931
|
+
|
|
2932
|
+
// Determine risk from task-level known_risks or overall risk
|
|
2933
|
+
const risk = task.risk || task.risk_level || 'medium';
|
|
2934
|
+
const confidence = typeof task.confidence === 'number'
|
|
2935
|
+
? task.confidence
|
|
2936
|
+
: riskToConfidence(risk);
|
|
2937
|
+
const predictedHealthDelta = Object.prototype.hasOwnProperty.call(task, 'predicted_health_delta')
|
|
2938
|
+
? task.predicted_health_delta
|
|
2939
|
+
: riskToHealthDelta(risk);
|
|
2940
|
+
const source = task.source || 'phase_3';
|
|
2941
|
+
|
|
2942
|
+
const prediction = {
|
|
2943
|
+
task_id: taskId,
|
|
2944
|
+
timestamp: now,
|
|
2945
|
+
expected_files: expectedFiles,
|
|
2946
|
+
risk: String(risk).toLowerCase(),
|
|
2947
|
+
confidence: Math.round(confidence * 100) / 100,
|
|
2948
|
+
predicted_health_delta: predictedHealthDelta,
|
|
2949
|
+
source,
|
|
2950
|
+
};
|
|
2951
|
+
|
|
2952
|
+
// Remove existing prediction for same task_id (update semantics)
|
|
2953
|
+
existing.predictions = existing.predictions.filter(p => p.task_id !== taskId);
|
|
2954
|
+
existing.predictions.push(prediction);
|
|
2955
|
+
}
|
|
2956
|
+
|
|
2957
|
+
// Atomic write: tmp file + rename
|
|
2958
|
+
const tmpPath = PREDICTIONS_PATH + '.tmp.' + process.pid;
|
|
2959
|
+
try {
|
|
2960
|
+
fs.writeFileSync(tmpPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
2961
|
+
fs.renameSync(tmpPath, PREDICTIONS_PATH);
|
|
2962
|
+
} catch (e) {
|
|
2963
|
+
warnings.push(`Phase 3 prediction: atomic write failed: ${e.message}`);
|
|
2964
|
+
// Cleanup tmp if rename failed
|
|
2965
|
+
try { fs.unlinkSync(tmpPath); } catch { /* ignore */ }
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
|
|
2969
|
+
// ═══════════════════════════════════════
|
|
2970
|
+
// Exit Logic
|
|
2971
|
+
// ═══════════════════════════════════════
|
|
2972
|
+
|
|
2973
|
+
function exitPhase(phase, taskId, data, specialistName = null, thinkingTags = null) {
|
|
2974
|
+
const chart = readChart(taskId);
|
|
2975
|
+
const phaseKey = String(phase);
|
|
2976
|
+
const warnings = [];
|
|
2977
|
+
|
|
2978
|
+
// Ensure phase record exists (in case enter was skipped)
|
|
2979
|
+
if (!chart.phases[phaseKey]) {
|
|
2980
|
+
chart.phases[phaseKey] = {
|
|
2981
|
+
entered_at: null,
|
|
2982
|
+
exited_at: null,
|
|
2983
|
+
status: 'active',
|
|
2984
|
+
modules_loaded: [],
|
|
2985
|
+
checklist_items: PHASE_CHECKLISTS[phase] || [],
|
|
2986
|
+
checklist_result: null,
|
|
2987
|
+
data: {},
|
|
2988
|
+
};
|
|
2989
|
+
warnings.push(`Phase ${phase} had no entry record — retroactively created`);
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2992
|
+
const phaseRecord = chart.phases[phaseKey];
|
|
2993
|
+
const mergedData = { ...(phaseRecord.data || {}), ...(data || {}) };
|
|
2994
|
+
const rules = PHASE_EXIT_RULES[phase];
|
|
2995
|
+
|
|
2996
|
+
// TASK_1017: Write thinking tags to thinking_log.jsonl (runs before block check
|
|
2997
|
+
// so self-annotation data is never lost even if phase exit is blocked)
|
|
2998
|
+
if (Array.isArray(thinkingTags) && thinkingTags.length > 0) {
|
|
2999
|
+
// Max 3 tags per phase (aligns with TASK_1018 top-3 cap)
|
|
3000
|
+
let tagsToProcess = thinkingTags;
|
|
3001
|
+
if (thinkingTags.length > 3) {
|
|
3002
|
+
const truncMsg = `thinking_tag: ${thinkingTags.length} tags exceeds max 3 — truncating to first 3`;
|
|
3003
|
+
console.error(truncMsg);
|
|
3004
|
+
warnings.push(truncMsg);
|
|
3005
|
+
tagsToProcess = thinkingTags.slice(0, 3);
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
// Validate tags against runtime-loaded principles from kernel.yaml.
|
|
3009
|
+
// If kernel.yaml absent/unreadable, skip validation (accept any tag, no warning).
|
|
3010
|
+
const knownTags = getKnownThinkingTags();
|
|
3011
|
+
const validTags = [];
|
|
3012
|
+
for (const tag of tagsToProcess) {
|
|
3013
|
+
if (knownTags === null || knownTags.has(tag)) {
|
|
3014
|
+
validTags.push(tag);
|
|
3015
|
+
} else {
|
|
3016
|
+
const warnMsg = `thinking_tag: unknown tag "${tag}" — not in kernel.yaml principles, accepted for forward compat`;
|
|
3017
|
+
console.error(warnMsg);
|
|
3018
|
+
warnings.push(warnMsg);
|
|
3019
|
+
validTags.push(tag);
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
|
|
3023
|
+
const logEntry = {
|
|
3024
|
+
timestamp: new Date().toISOString(),
|
|
3025
|
+
task_id: taskId,
|
|
3026
|
+
phase,
|
|
3027
|
+
specialist: specialistName || null,
|
|
3028
|
+
tags: validTags,
|
|
3029
|
+
};
|
|
3030
|
+
|
|
3031
|
+
try {
|
|
3032
|
+
const logDir = path.dirname(THINKING_LOG_PATH);
|
|
3033
|
+
if (!fs.existsSync(logDir)) fs.mkdirSync(logDir, { recursive: true });
|
|
3034
|
+
fs.appendFileSync(THINKING_LOG_PATH, JSON.stringify(logEntry) + '\n', 'utf8');
|
|
3035
|
+
mergedData.thinking_tags_written = validTags.length;
|
|
3036
|
+
} catch (e) {
|
|
3037
|
+
warnings.push(`thinking_tag: failed to write thinking_log.jsonl: ${e.message}`);
|
|
3038
|
+
mergedData.thinking_tags_written = 0;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
// Validate required fields
|
|
3043
|
+
if (rules && rules.required && rules.required.length > 0) {
|
|
3044
|
+
const missing = rules.required.filter((field) => {
|
|
3045
|
+
const validator = FIELD_VALIDATORS[field];
|
|
3046
|
+
return validator ? !validator(mergedData) : !(field in mergedData);
|
|
3047
|
+
});
|
|
3048
|
+
|
|
3049
|
+
// TASK_1104: Phase 0 ritual stage logging (every exit attempt, pass or fail)
|
|
3050
|
+
if (phase === 0) {
|
|
3051
|
+
const ritualStages = [
|
|
3052
|
+
{ stage: 'consent', field: 'consent_string' },
|
|
3053
|
+
{ stage: 'mirror', field: 'mirror_acknowledged' },
|
|
3054
|
+
{ stage: 'inheritance', field: 'inheritance_grounded' },
|
|
3055
|
+
];
|
|
3056
|
+
for (const { stage, field } of ritualStages) {
|
|
3057
|
+
const validator = FIELD_VALIDATORS[field];
|
|
3058
|
+
const ok = validator ? validator(mergedData) : (field in mergedData);
|
|
3059
|
+
appendRitualLog(stage, ok ? 'completed' : 'skipped', { task_id: taskId, phase, field });
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// TASK_1190: canary stage writer — 4th ritual marker, not covered by the
|
|
3063
|
+
// field-validator loop above (which handles consent/mirror/inheritance only).
|
|
3064
|
+
// Fires when stdout_markers_emitted self-report contains CANARY_OK literal.
|
|
3065
|
+
if (Array.isArray(mergedData.stdout_markers_emitted) &&
|
|
3066
|
+
mergedData.stdout_markers_emitted.includes('RITUAL::THRESHOLD::CANARY_OK')) {
|
|
3067
|
+
appendRitualLog('canary', 'completed', { task_id: taskId, phase });
|
|
3068
|
+
} else {
|
|
3069
|
+
appendRitualLog('canary', 'skipped', { task_id: taskId, phase, reason: 'CANARY_OK not in stdout_markers_emitted' });
|
|
3070
|
+
}
|
|
3071
|
+
|
|
3072
|
+
// TASK_1113: transcript verification — cross-check self-reported
|
|
3073
|
+
// stdout_markers_emitted against the live conversation jsonl. If the
|
|
3074
|
+
// grep can locate the transcript AND any required marker is absent,
|
|
3075
|
+
// block the exit. If no transcript can be found, record a warning
|
|
3076
|
+
// but do not hard-block (avoid CI false positives).
|
|
3077
|
+
// TASK_1190: when primary path fails (markers missing OR transcript not
|
|
3078
|
+
// found), try ritual_log fallback before blocking.
|
|
3079
|
+
if (Array.isArray(mergedData.stdout_markers_emitted) && mergedData.stdout_markers_emitted.length > 0) {
|
|
3080
|
+
// TASK_1170: use shared const instead of inline array
|
|
3081
|
+
const tx = verifyStdoutMarkersInTranscript(REQUIRED_STDOUT_MARKERS, 200);
|
|
3082
|
+
if (tx.checked && tx.missing.length === 0) {
|
|
3083
|
+
// Primary path succeeded — no change to existing behavior
|
|
3084
|
+
appendRitualLog('stdout_markers', 'completed', { task_id: taskId, phase, transcript: tx.transcript });
|
|
3085
|
+
} else {
|
|
3086
|
+
// Primary path did not fully succeed: either transcript not found (!tx.checked)
|
|
3087
|
+
// or markers missing (tx.checked && tx.missing.length > 0).
|
|
3088
|
+
// TASK_1190: attempt ritual_log fallback before deciding to block.
|
|
3089
|
+
const fb = verifyStdoutMarkersViaRitualLog(REQUIRED_STDOUT_MARKERS);
|
|
3090
|
+
if (fb.accepted) {
|
|
3091
|
+
// Fallback accepted — surface warning but allow exit
|
|
3092
|
+
const missedList = tx.checked ? tx.missing.join(', ') : `(transcript not found: ${tx.note || 'unknown'})`;
|
|
3093
|
+
warnings.push(`Phase 0 exit accepted via ritual_log fallback: transcript grep missed [${missedList}], ritual_log confirmed [${fb.confirmed.join(', ')}]`);
|
|
3094
|
+
appendRitualLog('stdout_markers', 'completed', { task_id: taskId, phase, path: 'ritual_log_fallback', transcript: tx.transcript });
|
|
3095
|
+
} else if (tx.checked && tx.missing.length > 0) {
|
|
3096
|
+
// Both paths checked: transcript found but markers absent, ritual_log also insufficient
|
|
3097
|
+
phaseRecord.status = 'blocked';
|
|
3098
|
+
phaseRecord.checklist_result = 'blocked';
|
|
3099
|
+
appendRitualLog('stdout_markers', 'skipped', { task_id: taskId, phase, missing: tx.missing, transcript: tx.transcript, fallback_note: fb.note || `ritual_log missing: [${fb.missing.join(', ')}]` });
|
|
3100
|
+
if (warnings.length > 0) chart.vitals.warnings.push(...warnings);
|
|
3101
|
+
writeChart(chart);
|
|
3102
|
+
// TASK_1170: actionable hint — bilingual, includes per-marker counts
|
|
3103
|
+
const hintZh = 'markers 必須以 user-visible plain text literal 形式出現在最近 200 行 conversation jsonl;' +
|
|
3104
|
+
'若 count=0 但你確定有輸出,可能原因:(a) 被 thinking block 包住,(b) 被 markdown code fence 包住,' +
|
|
3105
|
+
'(c) banner 或後續輸出太長把 markers 擠出 lookback 窗口 — 請在最後一段 user-visible 文字裡以純文字列出 4 條 marker 再呼叫 exit';
|
|
3106
|
+
const hintEn = 'Markers must appear as user-visible plain text literals in the last 200 lines of conversation jsonl. ' +
|
|
3107
|
+
'If count=0 but you are certain you emitted them, likely causes: ' +
|
|
3108
|
+
'(a) wrapped in a thinking block, (b) wrapped in a markdown code fence, ' +
|
|
3109
|
+
'(c) banner or subsequent output pushed markers out of the 200-line lookback window — ' +
|
|
3110
|
+
'emit all 4 markers as bare plain text in your final user-visible paragraph, then call exit again.';
|
|
3111
|
+
return outputResult({
|
|
3112
|
+
phase,
|
|
3113
|
+
action: 'exit',
|
|
3114
|
+
modules_loaded: phaseRecord.modules_loaded,
|
|
3115
|
+
module_content: {},
|
|
3116
|
+
checklist: phaseRecord.checklist_items,
|
|
3117
|
+
warnings,
|
|
3118
|
+
blocked: true,
|
|
3119
|
+
transcript_marker_counts: tx.counts,
|
|
3120
|
+
hint: hintZh + ' | ' + hintEn,
|
|
3121
|
+
block_reason: `Phase 0 exit BLOCKED: transcript grep of ${tx.transcript} did not find: [${tx.missing.join(', ')}]; ritual_log fallback also failed (missing stages: [${fb.missing.join(', ')}]${fb.note ? ', ' + fb.note : ''}). Emit the literal RITUAL::* markers to stdout during the ritual, then retry exit.`,
|
|
3122
|
+
});
|
|
3123
|
+
} else {
|
|
3124
|
+
// Transcript not found (!tx.checked) AND ritual_log fallback also failed
|
|
3125
|
+
if (fb.note === 'ritual_log missing') {
|
|
3126
|
+
// Both paths are absent — block (edge case a)
|
|
3127
|
+
phaseRecord.status = 'blocked';
|
|
3128
|
+
phaseRecord.checklist_result = 'blocked';
|
|
3129
|
+
appendRitualLog('stdout_markers', 'skipped', { task_id: taskId, phase, transcript_note: tx.note, fallback_note: fb.note });
|
|
3130
|
+
if (warnings.length > 0) chart.vitals.warnings.push(...warnings);
|
|
3131
|
+
writeChart(chart);
|
|
3132
|
+
return outputResult({
|
|
3133
|
+
phase,
|
|
3134
|
+
action: 'exit',
|
|
3135
|
+
modules_loaded: phaseRecord.modules_loaded,
|
|
3136
|
+
module_content: {},
|
|
3137
|
+
checklist: phaseRecord.checklist_items,
|
|
3138
|
+
warnings,
|
|
3139
|
+
blocked: true,
|
|
3140
|
+
block_reason: `Phase 0 exit BLOCKED: transcript not found (${tx.note || 'unknown'}) and ritual_log fallback also failed (${fb.note}). Both verification paths unavailable.`,
|
|
3141
|
+
});
|
|
3142
|
+
} else {
|
|
3143
|
+
// Transcript not found but ritual_log exists with insufficient entries
|
|
3144
|
+
phaseRecord.status = 'blocked';
|
|
3145
|
+
phaseRecord.checklist_result = 'blocked';
|
|
3146
|
+
appendRitualLog('stdout_markers', 'skipped', { task_id: taskId, phase, transcript_note: tx.note, fallback_missing: fb.missing });
|
|
3147
|
+
if (warnings.length > 0) chart.vitals.warnings.push(...warnings);
|
|
3148
|
+
writeChart(chart);
|
|
3149
|
+
return outputResult({
|
|
3150
|
+
phase,
|
|
3151
|
+
action: 'exit',
|
|
3152
|
+
modules_loaded: phaseRecord.modules_loaded,
|
|
3153
|
+
module_content: {},
|
|
3154
|
+
checklist: phaseRecord.checklist_items,
|
|
3155
|
+
warnings,
|
|
3156
|
+
blocked: true,
|
|
3157
|
+
block_reason: `Phase 0 exit BLOCKED: transcript not found (${tx.note || 'unknown'}); ritual_log fallback also insufficient (missing stages: [${fb.missing.join(', ')}]). Both verification paths failed.`,
|
|
3158
|
+
});
|
|
3159
|
+
}
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
}
|
|
3164
|
+
|
|
3165
|
+
if (missing.length > 0) {
|
|
3166
|
+
phaseRecord.status = 'blocked';
|
|
3167
|
+
phaseRecord.checklist_result = 'blocked';
|
|
3168
|
+
if (warnings.length > 0) chart.vitals.warnings.push(...warnings);
|
|
3169
|
+
writeChart(chart);
|
|
3170
|
+
return outputResult({
|
|
3171
|
+
phase,
|
|
3172
|
+
action: 'exit',
|
|
3173
|
+
modules_loaded: phaseRecord.modules_loaded,
|
|
3174
|
+
module_content: {},
|
|
3175
|
+
checklist: phaseRecord.checklist_items,
|
|
3176
|
+
warnings,
|
|
3177
|
+
blocked: true,
|
|
3178
|
+
block_reason: `Phase ${phase} exit BLOCKED: missing required fields [${missing.join(', ')}]. ${rules.description}`,
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
|
|
3183
|
+
// Phase 1 special handling: persist to chart-level fields
|
|
3184
|
+
if (phase === 1) {
|
|
3185
|
+
if (mergedData.chief_complaint) {
|
|
3186
|
+
chart.chief_complaint = mergedData.chief_complaint;
|
|
3187
|
+
}
|
|
3188
|
+
if (mergedData.recall_results) {
|
|
3189
|
+
chart.history.recall_results = Array.isArray(mergedData.recall_results)
|
|
3190
|
+
? mergedData.recall_results
|
|
3191
|
+
: [mergedData.recall_results];
|
|
3192
|
+
}
|
|
3193
|
+
if (mergedData.research_notes) {
|
|
3194
|
+
chart.history.research_notes = mergedData.research_notes;
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
// Phase 3 special handling: record predictions for designed tasks (TASK_914)
|
|
3199
|
+
if (phase === 3) {
|
|
3200
|
+
const tasks = mergedData.tasks || mergedData.designed_tasks || mergedData.task_list || [];
|
|
3201
|
+
if (Array.isArray(tasks) && tasks.length > 0) {
|
|
3202
|
+
writePredictions(tasks, warnings);
|
|
3203
|
+
mergedData.predictions_recorded = tasks.length;
|
|
3204
|
+
} else {
|
|
3205
|
+
warnings.push('Phase 3 prediction: no tasks array found in exit data (expected data.tasks) — predictions not recorded');
|
|
3206
|
+
mergedData.predictions_recorded = 0;
|
|
3207
|
+
}
|
|
3208
|
+
}
|
|
3209
|
+
|
|
3210
|
+
// Phase 3: capture design inputs for post-complete attribution (TASK_1081)
|
|
3211
|
+
if (phase === 3 && taskId) {
|
|
3212
|
+
try {
|
|
3213
|
+
const dr = require(path.join(BASE_DIR, '.specialist', 'tools', 'design-reasoning.js'));
|
|
3214
|
+
const tasks3 = mergedData.tasks || mergedData.designed_tasks || mergedData.task_list || [];
|
|
3215
|
+
|
|
3216
|
+
const explicitPrinciples = Array.isArray(mergedData.principles_applied) ? mergedData.principles_applied : [];
|
|
3217
|
+
const mergedPrinciples = Array.isArray(thinkingTags) && thinkingTags.length > 0
|
|
3218
|
+
? Array.from(new Set([...explicitPrinciples, ...thinkingTags]))
|
|
3219
|
+
: explicitPrinciples;
|
|
3220
|
+
|
|
3221
|
+
const designEntry = dr.recordDesignReasoning({
|
|
3222
|
+
taskId,
|
|
3223
|
+
principlesApplied: mergedPrinciples,
|
|
3224
|
+
guardsApplied: Array.isArray(mergedData.guards_applied) ? mergedData.guards_applied : [],
|
|
3225
|
+
formationSelected: mergedData.formation_selected || mergedData.formation || null,
|
|
3226
|
+
subtaskCount: Array.isArray(tasks3) && tasks3.length > 0 ? tasks3.length : (mergedData.subtask_count || null),
|
|
3227
|
+
splitRationale: mergedData.split_rationale || null,
|
|
3228
|
+
dependencyAssumptions: Array.isArray(mergedData.dependency_assumptions) ? mergedData.dependency_assumptions : [],
|
|
3229
|
+
riskAssessment: mergedData.risk_assessment || null,
|
|
3230
|
+
notes: mergedData.design_notes || mergedData.notes || null,
|
|
3231
|
+
});
|
|
3232
|
+
|
|
3233
|
+
mergedData.design_inputs_recorded = true;
|
|
3234
|
+
mergedData.design_inputs_timestamp = designEntry.timestamp;
|
|
3235
|
+
} catch (e) {
|
|
3236
|
+
warnings.push(`Phase 3 design capture: failed to record design inputs: ${e.message}`);
|
|
3237
|
+
mergedData.design_inputs_recorded = false;
|
|
3238
|
+
}
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
// Phase 9 special handling: increment fix_count for fix tasks
|
|
3242
|
+
if (phase === 9 && rules && rules.trackFix) {
|
|
3243
|
+
if (mergedData.is_fix_task === true || mergedData.fix_task === true) {
|
|
3244
|
+
chart.vitals.fix_count += 1;
|
|
3245
|
+
}
|
|
3246
|
+
}
|
|
3247
|
+
|
|
3248
|
+
// Phase 9: validate progress notes (TASK_579)
|
|
3249
|
+
if (phase === 9) {
|
|
3250
|
+
const { validation, warnings: noteWarnings } = validateProgressNotes(chart, taskId);
|
|
3251
|
+
mergedData.progress_note_validation = validation;
|
|
3252
|
+
if (noteWarnings.length > 0) warnings.push(...noteWarnings);
|
|
3253
|
+
}
|
|
3254
|
+
|
|
3255
|
+
// Phase 11: validate Ansuz identity before allowing self-model writes (TASK_869)
|
|
3256
|
+
if (phase === 11) {
|
|
3257
|
+
const hasSelfModelUpdate = mergedData.self_model_update !== undefined && mergedData.self_model_update !== null;
|
|
3258
|
+
if (hasSelfModelUpdate) {
|
|
3259
|
+
if (specialistName !== 'ansuz') {
|
|
3260
|
+
// Reject: non-Ansuz session attempting self-model write
|
|
3261
|
+
phaseRecord.status = 'blocked';
|
|
3262
|
+
phaseRecord.checklist_result = 'blocked';
|
|
3263
|
+
if (warnings.length > 0) chart.vitals.warnings.push(...warnings);
|
|
3264
|
+
writeChart(chart);
|
|
3265
|
+
return outputResult({
|
|
3266
|
+
phase,
|
|
3267
|
+
action: 'exit',
|
|
3268
|
+
modules_loaded: phaseRecord.modules_loaded,
|
|
3269
|
+
module_content: {},
|
|
3270
|
+
checklist: phaseRecord.checklist_items,
|
|
3271
|
+
warnings,
|
|
3272
|
+
blocked: true,
|
|
3273
|
+
block_reason: `Phase 11 self-model write BLOCKED: caller must be Ansuz specialist (got: ${specialistName || 'none'}). Pass --specialist=ansuz to exit Phase 11 with self-model updates.`,
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
// Ansuz confirmed — write growth-only fields to .kenaz/ansuz_self_model.json
|
|
3277
|
+
const writeResult = writeSelfModelGrowth(mergedData.self_model_update);
|
|
3278
|
+
if (writeResult.written) {
|
|
3279
|
+
mergedData.self_model_written = true;
|
|
3280
|
+
} else {
|
|
3281
|
+
warnings.push(`Phase 11: self-model growth write failed: ${writeResult.error}`);
|
|
3282
|
+
mergedData.self_model_written = false;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
// Phase 11: write session report with emptiness practice + cognitive clarity score (TASK_1207)
|
|
3288
|
+
if (phase === 11) {
|
|
3289
|
+
try {
|
|
3290
|
+
const report = {};
|
|
3291
|
+
// Read existing report if present (Phase 11 may be re-entered)
|
|
3292
|
+
if (fs.existsSync(SESSION_REPORT_PATH)) {
|
|
3293
|
+
try { Object.assign(report, JSON.parse(fs.readFileSync(SESSION_REPORT_PATH, 'utf8'))); } catch { /* start fresh */ }
|
|
3294
|
+
}
|
|
3295
|
+
|
|
3296
|
+
// Emptiness practice: released items from exit data
|
|
3297
|
+
if (Array.isArray(mergedData.emptiness_released_items)) {
|
|
3298
|
+
report.emptiness_released_items = mergedData.emptiness_released_items;
|
|
3299
|
+
}
|
|
3300
|
+
|
|
3301
|
+
// Cognitive clarity score from exit data
|
|
3302
|
+
if (typeof mergedData.cognitive_clarity_score === 'number'
|
|
3303
|
+
&& mergedData.cognitive_clarity_score >= 0
|
|
3304
|
+
&& mergedData.cognitive_clarity_score <= 1) {
|
|
3305
|
+
report.cognitive_clarity_score = mergedData.cognitive_clarity_score;
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
// Write report if either field is present
|
|
3309
|
+
if (report.emptiness_released_items || report.cognitive_clarity_score !== undefined) {
|
|
3310
|
+
report.schema_version = report.schema_version || '1.0.0';
|
|
3311
|
+
report.session_id = taskId || report.session_id || null;
|
|
3312
|
+
report.timestamp = new Date().toISOString();
|
|
3313
|
+
const reportDir = path.dirname(SESSION_REPORT_PATH);
|
|
3314
|
+
if (!fs.existsSync(reportDir)) fs.mkdirSync(reportDir, { recursive: true });
|
|
3315
|
+
fs.writeFileSync(SESSION_REPORT_PATH, JSON.stringify(report, null, 2), 'utf8');
|
|
3316
|
+
mergedData.session_report_written = true;
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3319
|
+
// Also write to THIS session's handoff slot if it exists (augment, not replace).
|
|
3320
|
+
// TASK_1829: per-session slot via readOwnHandoff — augmenting the "latest"
|
|
3321
|
+
// slot could write into another session's handoff; the legacy singular slot
|
|
3322
|
+
// is retired and never existed on this path anymore (fields were silently lost).
|
|
3323
|
+
try {
|
|
3324
|
+
const own = readOwnSessionHandoff();
|
|
3325
|
+
if (own.handoff && own.sessionId) {
|
|
3326
|
+
const handoff = own.handoff;
|
|
3327
|
+
if (Array.isArray(mergedData.emptiness_released_items)) {
|
|
3328
|
+
handoff.emptiness_released_items = mergedData.emptiness_released_items;
|
|
3329
|
+
}
|
|
3330
|
+
if (typeof mergedData.cognitive_clarity_score === 'number') {
|
|
3331
|
+
handoff.cognitive_clarity_score = mergedData.cognitive_clarity_score;
|
|
3332
|
+
}
|
|
3333
|
+
handoffResolver.writeHandoff(resolveHandoffProjectRoot(), own.sessionId, handoff);
|
|
3334
|
+
}
|
|
3335
|
+
} catch { /* graceful degradation — handoff augment is best-effort */ }
|
|
3336
|
+
} catch (e) {
|
|
3337
|
+
warnings.push(`Phase 11 session report write failed: ${e.message}`);
|
|
3338
|
+
mergedData.session_report_written = false;
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
// Phase 0: auto-increment sessions_observed for all active/proposed rules (TASK_959)
|
|
3343
|
+
if (phase === 0) {
|
|
3344
|
+
if (!mergedData.dynamic_user_context) {
|
|
3345
|
+
mergedData.dynamic_user_context = buildDynamicUserContext(warnings);
|
|
3346
|
+
}
|
|
3347
|
+
try {
|
|
3348
|
+
const ruleEngine = require(path.join(__dirname, 'rule-engine.js'));
|
|
3349
|
+
const trackResult = ruleEngine.autoTrackSession();
|
|
3350
|
+
mergedData.rules_auto_tracked = true;
|
|
3351
|
+
mergedData.rules_tracked_count = trackResult.total;
|
|
3352
|
+
} catch (e) {
|
|
3353
|
+
warnings.push(`Phase 0 rule auto-tracking failed: ${e.message}`);
|
|
3354
|
+
mergedData.rules_auto_tracked = false;
|
|
3355
|
+
}
|
|
3356
|
+
}
|
|
3357
|
+
|
|
3358
|
+
// Lesson guard acted-on recording (TASK_1076)
|
|
3359
|
+
// Specialist reports which guard IDs demonstrably influenced a decision via
|
|
3360
|
+
// lesson_guards_acted_on: ["L-001", ...] in phase exit data. Phase-gate records
|
|
3361
|
+
// it automatically so specialists don't need to call lesson-guard.js directly.
|
|
3362
|
+
const actedOnIds = mergedData.lesson_guards_acted_on;
|
|
3363
|
+
if (Array.isArray(actedOnIds) && actedOnIds.length > 0) {
|
|
3364
|
+
try {
|
|
3365
|
+
const lessonGuardModule = require(path.join(BASE_DIR, '.specialist', 'tools', 'lesson-guard.js'));
|
|
3366
|
+
const sessionId = String(taskId || `phase-${phase}-${new Date().toISOString()}`);
|
|
3367
|
+
let actedOnRecorded = 0;
|
|
3368
|
+
for (const guardId of actedOnIds) {
|
|
3369
|
+
if (typeof guardId === 'string' && /^L-\d+$/.test(guardId)) {
|
|
3370
|
+
const result = lessonGuardModule.recordActedOn(guardId, sessionId);
|
|
3371
|
+
if (result && result.ok) actedOnRecorded += 1;
|
|
3372
|
+
}
|
|
3373
|
+
}
|
|
3374
|
+
mergedData.lesson_guards_acted_on_recorded = actedOnRecorded;
|
|
3375
|
+
} catch { /* graceful degradation — lesson-guard.js unavailable */ }
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
// Unified memory-to-action acted-on recording (TASK_1072)
|
|
3379
|
+
// Generalised version of lesson_guards_acted_on. Specialist reports via
|
|
3380
|
+
// memory_acted_on: [
|
|
3381
|
+
// { source, source_id, action_type, reason, rune_type?, confidence? },
|
|
3382
|
+
// ...
|
|
3383
|
+
// ]
|
|
3384
|
+
// covering all six memory sources (handoff/episode/rune/sigil/self_model/
|
|
3385
|
+
// lesson_guard). Delegates to memory-action-log.js which writes the unified
|
|
3386
|
+
// jsonl log and bridges lesson_guard entries back into lesson-guard.js.
|
|
3387
|
+
const memoryActedOn = mergedData.memory_acted_on;
|
|
3388
|
+
if (Array.isArray(memoryActedOn) && memoryActedOn.length > 0) {
|
|
3389
|
+
try {
|
|
3390
|
+
const memLog = require(path.join(BASE_DIR, '.specialist', 'tools', 'memory-action-log.js'));
|
|
3391
|
+
const result = memLog.recordActedOnBatch(memoryActedOn, String(taskId || `phase-${phase}`), phase);
|
|
3392
|
+
mergedData.memory_acted_on_recorded = result.recorded;
|
|
3393
|
+
if (result.failed && result.failed.length > 0) {
|
|
3394
|
+
mergedData.memory_acted_on_failed = result.failed.map((f) => ({
|
|
3395
|
+
source_id: f.item && f.item.source_id,
|
|
3396
|
+
error: f.error,
|
|
3397
|
+
}));
|
|
3398
|
+
for (const f of result.failed) {
|
|
3399
|
+
warnings.push(`memory_acted_on: ${f.error} (source_id=${f.item && f.item.source_id})`);
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
} catch (e) {
|
|
3403
|
+
// Graceful degradation — memory-action-log.js unavailable should never
|
|
3404
|
+
// block a phase exit. Still surface the reason as a warning.
|
|
3405
|
+
warnings.push(`memory_acted_on: module unavailable (${e.message})`);
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
// L4: Handoff staleness nudge at milestone-phase exits (TASK_1396)
|
|
3410
|
+
// Phases 1, 3, 9, 11 are milestone checkpoints — handoff must stay live.
|
|
3411
|
+
// Soft warning only on first iteration.
|
|
3412
|
+
// If this soft warning fails to change behavior, the next iteration should
|
|
3413
|
+
// hard-block phase exit until handoff is fresh (generated_at within 5 min + last_milestone set).
|
|
3414
|
+
if ([1, 3, 9, 11].includes(phase)) {
|
|
3415
|
+
try {
|
|
3416
|
+
const HANDOFF_STALE_MS = 5 * 60 * 1000;
|
|
3417
|
+
let handoffWarn = null;
|
|
3418
|
+
// TASK_1829: check THIS session's per-session slot (readOwnHandoff), not the
|
|
3419
|
+
// retired singular file (always-missing → permanent false alarm) and not the
|
|
3420
|
+
// latest slot (another session's fresh handoff would false-pass — TASK_1825 class).
|
|
3421
|
+
const own = readOwnSessionHandoff();
|
|
3422
|
+
if (!own.handoff) {
|
|
3423
|
+
handoffWarn = 'missing';
|
|
3424
|
+
} else {
|
|
3425
|
+
const hd = own.handoff;
|
|
3426
|
+
if (!hd.cleared) {
|
|
3427
|
+
const genAt = hd.generated_at ? new Date(hd.generated_at).getTime() : null;
|
|
3428
|
+
const lastMilestone = (hd.isbar && hd.isbar.last_milestone) || hd.last_milestone || null;
|
|
3429
|
+
if (!genAt || (Date.now() - genAt) > HANDOFF_STALE_MS) {
|
|
3430
|
+
handoffWarn = 'stale_timestamp';
|
|
3431
|
+
} else if (!lastMilestone) {
|
|
3432
|
+
handoffWarn = 'empty_last_milestone';
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
}
|
|
3436
|
+
if (handoffWarn) {
|
|
3437
|
+
warnings.push(`phase-${phase}-exit: handoff stale or missing — write live_phase_exit update before next milestone`);
|
|
3438
|
+
}
|
|
3439
|
+
} catch { /* L4 check must never block phase exit */ }
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// Mark phase complete
|
|
3443
|
+
phaseRecord.exited_at = new Date().toISOString();
|
|
3444
|
+
phaseRecord.status = 'completed';
|
|
3445
|
+
phaseRecord.checklist_result = 'pass';
|
|
3446
|
+
phaseRecord.data = mergedData;
|
|
3447
|
+
|
|
3448
|
+
if (warnings.length > 0) {
|
|
3449
|
+
chart.vitals.warnings.push(...warnings);
|
|
3450
|
+
}
|
|
3451
|
+
|
|
3452
|
+
writeChart(chart);
|
|
3453
|
+
|
|
3454
|
+
// Phase 11: write cleared sentinel so downstream readers skip restore (TASK_1390).
|
|
3455
|
+
// TASK_1829: the sentinel goes into THIS session's per-session slot (the legacy
|
|
3456
|
+
// singular slot is retired). Existing slot fields (incl. the Phase 11 augment
|
|
3457
|
+
// above) are preserved under the sentinel instead of clobbered — readers gate on
|
|
3458
|
+
// cleared===true either way. generated_at is re-stamped so the freshness guard in
|
|
3459
|
+
// ansuz-handoff-snapshot.js does not overwrite a just-cleared slot.
|
|
3460
|
+
if (phase === 11) {
|
|
3461
|
+
try {
|
|
3462
|
+
const own = readOwnSessionHandoff();
|
|
3463
|
+
if (own.sessionId) {
|
|
3464
|
+
const cleared = Object.assign({}, own.handoff || {}, {
|
|
3465
|
+
cleared: true,
|
|
3466
|
+
session_id: own.sessionId,
|
|
3467
|
+
ended_at: new Date().toISOString(),
|
|
3468
|
+
generated_at: new Date().toISOString(),
|
|
3469
|
+
});
|
|
3470
|
+
handoffResolver.writeHandoff(resolveHandoffProjectRoot(), own.sessionId, cleared);
|
|
3471
|
+
}
|
|
3472
|
+
// No resolvable session identity → no slot can be attributed to this session;
|
|
3473
|
+
// nothing to clear (writing a latest/anonymous sentinel could clear another
|
|
3474
|
+
// session's live handoff — same own-vs-latest split as the L4 nudge).
|
|
3475
|
+
} catch { /* best-effort — never block Phase 11 exit */ }
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
// TASK_943: Check ansuz inbox for fatigue_warning messages at every phase boundary
|
|
3479
|
+
const exitInboxMessages = readAnsuzInboxMessages();
|
|
3480
|
+
|
|
3481
|
+
const exitResult = outputResult({
|
|
3482
|
+
phase,
|
|
3483
|
+
action: 'exit',
|
|
3484
|
+
modules_loaded: phaseRecord.modules_loaded,
|
|
3485
|
+
module_content: {},
|
|
3486
|
+
checklist: phaseRecord.checklist_items,
|
|
3487
|
+
inbox_messages: exitInboxMessages.length > 0 ? exitInboxMessages : null, // TASK_943: fatigue_warning delivery
|
|
3488
|
+
warnings,
|
|
3489
|
+
blocked: false,
|
|
3490
|
+
block_reason: null,
|
|
3491
|
+
});
|
|
3492
|
+
|
|
3493
|
+
// TASK_1400 + 2026-05-02 fix: Phase-exit auto-snapshot. Prior version only
|
|
3494
|
+
// printed a nudge, leaving the snapshot write to the agent — which routinely
|
|
3495
|
+
// missed it under momentum (see commit 852bb77f rationale). Now spawns the
|
|
3496
|
+
// snapshot CLI as a detached process so the write is system-level, not
|
|
3497
|
+
// instance-dependent. Errors swallowed; never blocks phase exit.
|
|
3498
|
+
if ([1, 3, 9, 11].includes(phase) && !exitResult.blocked) {
|
|
3499
|
+
const milestoneHint = (data && data.last_milestone)
|
|
3500
|
+
? data.last_milestone
|
|
3501
|
+
: `Phase ${phase} exit`;
|
|
3502
|
+
const fullMilestone = `Phase ${phase} exit: ${milestoneHint}`;
|
|
3503
|
+
try {
|
|
3504
|
+
const { spawn } = require('child_process');
|
|
3505
|
+
const cli = path.join((CLI_PROJECT_PATH || BASE_DIR), '.specialist', 'Ansuz', 'tools', 'ansuz-handoff-snapshot.js');
|
|
3506
|
+
if (fs.existsSync(cli)) {
|
|
3507
|
+
const snapshotArgs = [cli, '--trigger=live_phase_exit', `--last-milestone=${fullMilestone}`];
|
|
3508
|
+
// TASK_1829: forward our session id so the snapshot lands in THIS session's
|
|
3509
|
+
// per-session slot (keeps the L4 own-slot check coherent across milestones).
|
|
3510
|
+
const ownIds = resolveOwnHandoffSessionIds();
|
|
3511
|
+
if (ownIds.length > 0) snapshotArgs.push(`--session-id=${ownIds[0]}`);
|
|
3512
|
+
const child = spawn('node', snapshotArgs, {
|
|
3513
|
+
detached: true,
|
|
3514
|
+
stdio: 'ignore',
|
|
3515
|
+
cwd: (CLI_PROJECT_PATH || BASE_DIR),
|
|
3516
|
+
windowsHide: true,
|
|
3517
|
+
});
|
|
3518
|
+
child.unref();
|
|
3519
|
+
}
|
|
3520
|
+
} catch { /* best-effort — never block exit */ }
|
|
3521
|
+
process.stdout.write(
|
|
3522
|
+
`\n🔁 Phase ${phase} exit. Handoff auto-snapshot dispatched (live_phase_exit).\n`
|
|
3523
|
+
);
|
|
3524
|
+
}
|
|
3525
|
+
|
|
3526
|
+
return exitResult;
|
|
3527
|
+
}
|
|
3528
|
+
|
|
3529
|
+
// ═══════════════════════════════════════
|
|
3530
|
+
// Status
|
|
3531
|
+
// ═══════════════════════════════════════
|
|
3532
|
+
|
|
3533
|
+
function statusTask(taskId) {
|
|
3534
|
+
const chartPath = getChartReadPath(taskId);
|
|
3535
|
+
const chart = readChart(taskId);
|
|
3536
|
+
|
|
3537
|
+
return outputResult({
|
|
3538
|
+
task_id: taskId,
|
|
3539
|
+
chart_exists: fs.existsSync(chartPath),
|
|
3540
|
+
chief_complaint: chart.chief_complaint,
|
|
3541
|
+
vitals: chart.vitals,
|
|
3542
|
+
phases: Object.entries(chart.phases)
|
|
3543
|
+
.sort(([a], [b]) => Number(a) - Number(b))
|
|
3544
|
+
.map(([n, p]) => ({
|
|
3545
|
+
phase: Number(n),
|
|
3546
|
+
status: p.status,
|
|
3547
|
+
entered_at: p.entered_at,
|
|
3548
|
+
exited_at: p.exited_at,
|
|
3549
|
+
checklist_result: p.checklist_result,
|
|
3550
|
+
modules_loaded: p.modules_loaded,
|
|
3551
|
+
})),
|
|
3552
|
+
last_updated: chart.updated_at,
|
|
3553
|
+
});
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
// ═══════════════════════════════════════
|
|
3557
|
+
// Output helpers
|
|
3558
|
+
// ═══════════════════════════════════════
|
|
3559
|
+
|
|
3560
|
+
function outputResult(obj) {
|
|
3561
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
3562
|
+
// TASK_1104: blocked enter/exit must surface as non-zero exit code so
|
|
3563
|
+
// CI / headless dispatch can detect ritual failures without JSON parsing.
|
|
3564
|
+
if (obj && obj.blocked === true) {
|
|
3565
|
+
process.exitCode = 2;
|
|
3566
|
+
}
|
|
3567
|
+
return obj;
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
function fail(message) {
|
|
3571
|
+
console.error(JSON.stringify({ error: message }, null, 2));
|
|
3572
|
+
process.exit(1);
|
|
3573
|
+
}
|
|
3574
|
+
|
|
3575
|
+
// ═══════════════════════════════════════
|
|
3576
|
+
// CLI Argument Parser
|
|
3577
|
+
// ═══════════════════════════════════════
|
|
3578
|
+
|
|
3579
|
+
function parseArgs(argv) {
|
|
3580
|
+
const args = {};
|
|
3581
|
+
for (const arg of argv) {
|
|
3582
|
+
if (arg.startsWith('--')) {
|
|
3583
|
+
const eqIdx = arg.indexOf('=');
|
|
3584
|
+
if (eqIdx === -1) {
|
|
3585
|
+
args[arg.slice(2)] = true;
|
|
3586
|
+
} else {
|
|
3587
|
+
const key = arg.slice(2, eqIdx);
|
|
3588
|
+
const val = arg.slice(eqIdx + 1);
|
|
3589
|
+
args[key] = val;
|
|
3590
|
+
}
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
return args;
|
|
3594
|
+
}
|
|
3595
|
+
|
|
3596
|
+
// ═══════════════════════════════════════
|
|
3597
|
+
// Main CLI Entry Point
|
|
3598
|
+
// ═══════════════════════════════════════
|
|
3599
|
+
|
|
3600
|
+
if (require.main === module) {
|
|
3601
|
+
const [, , action, ...rest] = process.argv;
|
|
3602
|
+
const args = parseArgs(rest);
|
|
3603
|
+
|
|
3604
|
+
if (!action) {
|
|
3605
|
+
fail('Usage: node phase-gate.js <enter|exit|status|dump|note> [--phase=N] [--task=TASK_XXX] [--specialist=ansuz] [--data=\'{...}\'] [--thinking-tag=X,Y,Z]\n node phase-gate.js dump --all');
|
|
3606
|
+
}
|
|
3607
|
+
|
|
3608
|
+
// TASK_2374: append-only subcommand usage counter (measure-first; never throws).
|
|
3609
|
+
try {
|
|
3610
|
+
const f = process.env.KENAZ_USAGE_COUNTERS_FILE || resolveKenazPath('specialist', 'usage_counters.jsonl');
|
|
3611
|
+
fs.mkdirSync(path.dirname(f), { recursive: true });
|
|
3612
|
+
fs.appendFileSync(f, JSON.stringify({ ts: new Date().toISOString(), kind: 'phase-gate', name: action, task: args.task || null }) + '\n');
|
|
3613
|
+
} catch { /* counter must never break the gate */ }
|
|
3614
|
+
|
|
3615
|
+
switch (action) {
|
|
3616
|
+
case 'note': {
|
|
3617
|
+
// Merge a progress note (JSON object) from a local tmp file into the core chart.
|
|
3618
|
+
// Worker writes note to a project-local path, then calls this subcommand to avoid
|
|
3619
|
+
// cross-workspace Write tool calls (which trigger permission prompts across roots).
|
|
3620
|
+
// --project-path is handled at module load via CLI_PROJECT_PATH; SESSION_DIR's
|
|
3621
|
+
// PHYSICAL location stays on core, but TASK_2163's chart-path resolver keys the
|
|
3622
|
+
// file within it by project identity, so sub-project ids don't collide.
|
|
3623
|
+
if (!args.task) fail('--task=TASK_XXX is required');
|
|
3624
|
+
if (!args['from-file']) fail('--from-file=<path> is required');
|
|
3625
|
+
|
|
3626
|
+
const noteFilePath = path.resolve(args['from-file']);
|
|
3627
|
+
let noteRaw;
|
|
3628
|
+
try {
|
|
3629
|
+
noteRaw = fs.readFileSync(noteFilePath, 'utf8');
|
|
3630
|
+
// Strip BOM -- PowerShell Set-Content writes UTF-8 with BOM by default
|
|
3631
|
+
if (noteRaw.charCodeAt(0) === 0xFEFF) noteRaw = noteRaw.slice(1);
|
|
3632
|
+
} catch (e) {
|
|
3633
|
+
fail('--from-file read error: ' + e.message);
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3636
|
+
let noteEntry;
|
|
3637
|
+
try {
|
|
3638
|
+
noteEntry = JSON.parse(noteRaw);
|
|
3639
|
+
} catch (e) {
|
|
3640
|
+
fail('--from-file is not valid JSON: ' + e.message);
|
|
3641
|
+
}
|
|
3642
|
+
if (typeof noteEntry !== 'object' || noteEntry === null || Array.isArray(noteEntry)) {
|
|
3643
|
+
fail('Progress note must be a JSON object, not an array or primitive');
|
|
3644
|
+
}
|
|
3645
|
+
|
|
3646
|
+
// Read or create chart skeleton if absent (readChart returns createChart() when file missing)
|
|
3647
|
+
const noteTaskId = args.task;
|
|
3648
|
+
const chart = readChart(noteTaskId);
|
|
3649
|
+
if (!Array.isArray(chart.progress_notes)) {
|
|
3650
|
+
chart.progress_notes = [];
|
|
3651
|
+
}
|
|
3652
|
+
chart.progress_notes.push(noteEntry);
|
|
3653
|
+
|
|
3654
|
+
// Atomic write: write to .tmp then rename -- prevents partial-write corruption
|
|
3655
|
+
const chartPath = getChartPath(noteTaskId);
|
|
3656
|
+
const tmpPath = chartPath + '.tmp';
|
|
3657
|
+
chartPathLib.ensureChartDir(chartPath);
|
|
3658
|
+
chart.updated_at = new Date().toISOString();
|
|
3659
|
+
fs.writeFileSync(tmpPath, JSON.stringify(chart, null, 2), 'utf8');
|
|
3660
|
+
fs.renameSync(tmpPath, chartPath);
|
|
3661
|
+
|
|
3662
|
+
console.log('[phase-gate note] appended to ' + chartPath + ' (total notes: ' + chart.progress_notes.length + ')');
|
|
3663
|
+
break;
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
case 'dump': {
|
|
3667
|
+
if (!args.all) fail('--all is required: node phase-gate.js dump --all');
|
|
3668
|
+
dumpPhases();
|
|
3669
|
+
break;
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3672
|
+
case 'enter': {
|
|
3673
|
+
if (!args.phase) fail('--phase=N is required');
|
|
3674
|
+
const phase = parseInt(args.phase, 10);
|
|
3675
|
+
if (isNaN(phase) || phase < 0) fail('--phase must be a non-negative integer');
|
|
3676
|
+
// Phase 0 (session init) runs before any task exists — allow --task=none or omit
|
|
3677
|
+
if (phase !== 0 && !args.task) fail('--task=TASK_XXX is required');
|
|
3678
|
+
const enterTaskId = args.task || 'none';
|
|
3679
|
+
// --specialist=ansuz|amelia|... → passed as agentRole to Phase 0 recall filter
|
|
3680
|
+
enterPhase(phase, enterTaskId, args.specialist || null);
|
|
3681
|
+
break;
|
|
3682
|
+
}
|
|
3683
|
+
|
|
3684
|
+
case 'exit': {
|
|
3685
|
+
if (!args.phase) fail('--phase=N is required');
|
|
3686
|
+
const phase = parseInt(args.phase, 10);
|
|
3687
|
+
if (isNaN(phase) || phase < 0) fail('--phase must be a non-negative integer');
|
|
3688
|
+
// Phase 0 exit also allows --task=none
|
|
3689
|
+
if (phase !== 0 && !args.task) fail('--task=TASK_XXX is required');
|
|
3690
|
+
const exitTaskId = args.task || 'none';
|
|
3691
|
+
let data = {};
|
|
3692
|
+
if (args['data-file']) {
|
|
3693
|
+
// Read JSON from file (PowerShell/Codex safe — avoids shell quoting issues)
|
|
3694
|
+
try {
|
|
3695
|
+
let raw = require('fs').readFileSync(args['data-file'], 'utf8');
|
|
3696
|
+
// Strip BOM — PowerShell Set-Content writes UTF-8 with BOM by default
|
|
3697
|
+
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1);
|
|
3698
|
+
data = JSON.parse(raw);
|
|
3699
|
+
} catch (e) {
|
|
3700
|
+
fail(`--data-file read/parse error: ${e.message}`);
|
|
3701
|
+
}
|
|
3702
|
+
} else if (args.data) {
|
|
3703
|
+
try {
|
|
3704
|
+
data = JSON.parse(args.data);
|
|
3705
|
+
} catch (e) {
|
|
3706
|
+
fail(`--data is not valid JSON: ${e.message}\nTip: On PowerShell/Codex, use --data-file=path.json instead of --data='{...}'`);
|
|
3707
|
+
}
|
|
3708
|
+
}
|
|
3709
|
+
// TASK_1017: Parse --thinking-tag=X,Y,Z
|
|
3710
|
+
const thinkingTags = args['thinking-tag']
|
|
3711
|
+
? String(args['thinking-tag']).split(',').map(t => t.trim()).filter(Boolean)
|
|
3712
|
+
: null;
|
|
3713
|
+
exitPhase(phase, exitTaskId, data, args.specialist || null, thinkingTags);
|
|
3714
|
+
break;
|
|
3715
|
+
}
|
|
3716
|
+
|
|
3717
|
+
case 'status': {
|
|
3718
|
+
if (!args.task) fail('--task=TASK_XXX is required');
|
|
3719
|
+
statusTask(args.task);
|
|
3720
|
+
break;
|
|
3721
|
+
}
|
|
3722
|
+
|
|
3723
|
+
default:
|
|
3724
|
+
fail(`Unknown action: "${action}". Valid actions: enter, exit, status, dump, note`);
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
// ═══════════════════════════════════════
|
|
3729
|
+
// Module exports (for programmatic use)
|
|
3730
|
+
// ═══════════════════════════════════════
|
|
3731
|
+
|
|
3732
|
+
module.exports = {
|
|
3733
|
+
enterPhase,
|
|
3734
|
+
exitPhase,
|
|
3735
|
+
statusTask,
|
|
3736
|
+
readChart,
|
|
3737
|
+
writeChart,
|
|
3738
|
+
createChart,
|
|
3739
|
+
readTaskScopePaths,
|
|
3740
|
+
dumpPhases,
|
|
3741
|
+
loadPhaseInstructions,
|
|
3742
|
+
buildContextHints,
|
|
3743
|
+
recallEpisodicMemory,
|
|
3744
|
+
// TASK_869 seed protection
|
|
3745
|
+
verifySeedIntegrity,
|
|
3746
|
+
restoreSeedFromBackup,
|
|
3747
|
+
mergeSeedAndGrowth,
|
|
3748
|
+
writeSelfModelGrowth,
|
|
3749
|
+
// TASK_923 blind spot guard injection
|
|
3750
|
+
readBlindSpotGuards,
|
|
3751
|
+
// TASK_2340: exposed for gate-shape tests
|
|
3752
|
+
PHASE_CHECKLISTS,
|
|
3753
|
+
PHASE_EXIT_RULES,
|
|
3754
|
+
};
|