@cleocode/core 2026.4.58 → 2026.4.60

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.
Files changed (32) hide show
  1. package/dist/index.js +488 -11
  2. package/dist/index.js.map +3 -3
  3. package/dist/internal.js +497 -13
  4. package/dist/internal.js.map +3 -3
  5. package/dist/memory/graph-queries.d.ts.map +1 -1
  6. package/dist/store/agent-registry-accessor.d.ts +1 -1
  7. package/dist/store/brain-accessor.d.ts +57 -1
  8. package/dist/store/brain-accessor.d.ts.map +1 -1
  9. package/dist/store/brain-schema.d.ts +738 -0
  10. package/dist/store/brain-schema.d.ts.map +1 -1
  11. package/dist/store/brain-sqlite.d.ts.map +1 -1
  12. package/dist/store/nexus-schema.d.ts +1 -1
  13. package/dist/system/health.d.ts.map +1 -1
  14. package/dist/validation/doctor/checks.d.ts +7 -0
  15. package/dist/validation/doctor/checks.d.ts.map +1 -1
  16. package/migrations/drizzle-brain/20260416000001_t673-retrieval-log-plasticity-columns/migration.sql +57 -0
  17. package/migrations/drizzle-brain/20260416000002_t673-plasticity-events-expand/migration.sql +44 -0
  18. package/migrations/drizzle-brain/20260416000003_t673-page-edges-plasticity-columns/migration.sql +44 -0
  19. package/migrations/drizzle-brain/20260416000004_t673-new-plasticity-tables/migration.sql +73 -0
  20. package/package.json +8 -8
  21. package/src/memory/__tests__/brain-retrieval-m1.test.ts +250 -0
  22. package/src/memory/__tests__/brain-schema-m2-m3.test.ts +418 -0
  23. package/src/memory/__tests__/brain-schema-m4.test.ts +494 -0
  24. package/src/memory/brain-retrieval.ts +1 -1
  25. package/src/memory/graph-queries.ts +14 -0
  26. package/src/store/agent-registry-accessor.ts +1 -1
  27. package/src/store/brain-accessor.ts +120 -0
  28. package/src/store/brain-schema.ts +373 -1
  29. package/src/store/brain-sqlite.ts +123 -0
  30. package/src/system/health.ts +4 -1
  31. package/src/validation/doctor/checks.ts +107 -0
  32. package/src/validation/protocols/protocols-markdown/research.md +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -9039,9 +9045,11 @@ __export(brain_schema_exports, {
9039
9045
  BRAIN_STICKY_COLORS: () => BRAIN_STICKY_COLORS,
9040
9046
  BRAIN_STICKY_PRIORITIES: () => BRAIN_STICKY_PRIORITIES,
9041
9047
  BRAIN_STICKY_STATUSES: () => BRAIN_STICKY_STATUSES,
9048
+ brainConsolidationEvents: () => brainConsolidationEvents,
9042
9049
  brainDecisions: () => brainDecisions,
9043
9050
  brainLearnings: () => brainLearnings,
9044
9051
  brainMemoryLinks: () => brainMemoryLinks,
9052
+ brainModulators: () => brainModulators,
9045
9053
  brainObservations: () => brainObservations,
9046
9054
  brainPageEdges: () => brainPageEdges,
9047
9055
  brainPageNodes: () => brainPageNodes,
@@ -9049,10 +9057,11 @@ __export(brain_schema_exports, {
9049
9057
  brainPlasticityEvents: () => brainPlasticityEvents,
9050
9058
  brainRetrievalLog: () => brainRetrievalLog,
9051
9059
  brainSchemaMeta: () => brainSchemaMeta,
9052
- brainStickyNotes: () => brainStickyNotes
9060
+ brainStickyNotes: () => brainStickyNotes,
9061
+ brainWeightHistory: () => brainWeightHistory
9053
9062
  });
9054
9063
  import { sql as sql2 } from "drizzle-orm";
9055
- var BRAIN_MEMORY_TIERS, BRAIN_COGNITIVE_TYPES, BRAIN_SOURCE_CONFIDENCE, BRAIN_DECISION_TYPES, BRAIN_CONFIDENCE_LEVELS, BRAIN_OUTCOME_TYPES, BRAIN_PATTERN_TYPES, BRAIN_IMPACT_LEVELS, BRAIN_LINK_TYPES, BRAIN_OBSERVATION_TYPES2, BRAIN_OBSERVATION_SOURCE_TYPES, BRAIN_MEMORY_TYPES, BRAIN_STICKY_STATUSES, BRAIN_STICKY_COLORS, BRAIN_STICKY_PRIORITIES, brainDecisions, brainPatterns, brainLearnings, brainObservations, brainStickyNotes, brainMemoryLinks, brainSchemaMeta, BRAIN_NODE_TYPES, BRAIN_EDGE_TYPES, brainPageNodes, brainPageEdges, brainRetrievalLog, brainPlasticityEvents;
9064
+ var BRAIN_MEMORY_TIERS, BRAIN_COGNITIVE_TYPES, BRAIN_SOURCE_CONFIDENCE, BRAIN_DECISION_TYPES, BRAIN_CONFIDENCE_LEVELS, BRAIN_OUTCOME_TYPES, BRAIN_PATTERN_TYPES, BRAIN_IMPACT_LEVELS, BRAIN_LINK_TYPES, BRAIN_OBSERVATION_TYPES2, BRAIN_OBSERVATION_SOURCE_TYPES, BRAIN_MEMORY_TYPES, BRAIN_STICKY_STATUSES, BRAIN_STICKY_COLORS, BRAIN_STICKY_PRIORITIES, brainDecisions, brainPatterns, brainLearnings, brainObservations, brainStickyNotes, brainMemoryLinks, brainSchemaMeta, BRAIN_NODE_TYPES, BRAIN_EDGE_TYPES, brainPageNodes, brainPageEdges, brainRetrievalLog, brainPlasticityEvents, brainWeightHistory, brainModulators, brainConsolidationEvents;
9056
9065
  var init_brain_schema = __esm({
9057
9066
  "packages/core/src/store/brain-schema.ts"() {
9058
9067
  "use strict";
@@ -9558,13 +9567,75 @@ var init_brain_schema = __esm({
9558
9567
  * 'auto:contradiction-detected' | 'auto:consolidation' | 'manual'
9559
9568
  */
9560
9569
  provenance: text("provenance"),
9561
- createdAt: text("created_at").notNull().default(sql2`(datetime('now'))`)
9570
+ createdAt: text("created_at").notNull().default(sql2`(datetime('now'))`),
9571
+ // === T673-M3: Plasticity tracking columns ===
9572
+ /**
9573
+ * ISO 8601 timestamp of the last LTP event applied to this edge.
9574
+ * Used by the decay pass: edges with (now - last_reinforced_at) > decay_threshold_days
9575
+ * receive a per-day weight decay. Null = never reinforced (structural/semantic edges).
9576
+ * Only populated when plasticity_class IN ('hebbian', 'stdp').
9577
+ *
9578
+ * @task T706
9579
+ */
9580
+ lastReinforcedAt: text("last_reinforced_at"),
9581
+ /**
9582
+ * Count of LTP (potentiation) events applied to this edge lifetime.
9583
+ * Incremented on every LTP write. Used to compute stability_score.
9584
+ *
9585
+ * @task T706
9586
+ */
9587
+ reinforcementCount: integer("reinforcement_count").notNull().default(0),
9588
+ /**
9589
+ * Plasticity class governing which algorithm(s) write to this edge.
9590
+ *
9591
+ * - 'static': Non-plastic edge (structural, semantic, etc.). Immune to decay.
9592
+ * - 'hebbian': Written by strengthenCoRetrievedEdges. Subject to decay.
9593
+ * - 'stdp': Written or refined by applyStdpPlasticity. Subject to decay + LTD.
9594
+ *
9595
+ * Edges start 'static' for all non-co_retrieved types.
9596
+ * co_retrieved edges start 'hebbian' (seeded by M3 migration), can upgrade to 'stdp'.
9597
+ *
9598
+ * @task T706
9599
+ */
9600
+ plasticityClass: text("plasticity_class", {
9601
+ enum: ["static", "hebbian", "stdp"]
9602
+ }).notNull().default("static"),
9603
+ /**
9604
+ * ISO 8601 timestamp of the last LTD (depression) event on this edge.
9605
+ * Null = never depressed. Used for debugging and Studio viz animation.
9606
+ *
9607
+ * @task T706
9608
+ */
9609
+ lastDepressedAt: text("last_depressed_at"),
9610
+ /**
9611
+ * Count of LTD (depression) events applied to this edge lifetime.
9612
+ * Enables analysis of edges that are persistently weakened.
9613
+ *
9614
+ * @task T706
9615
+ */
9616
+ depressionCount: integer("depression_count").notNull().default(0),
9617
+ /**
9618
+ * Biological-analog stability score: 0.0 (unstable) – 1.0 (consolidated).
9619
+ *
9620
+ * Computed by runConsolidation decay pass as:
9621
+ * stability = tanh(reinforcement_count / 10) × exp(-(days_since_reinforced / 30))
9622
+ *
9623
+ * Null = not yet computed (new edges). Enables fast filtering in decay pass:
9624
+ * edges with stability > 0.9 skip the full decay recalculation.
9625
+ * Updated at session-end consolidation, NOT per-event.
9626
+ *
9627
+ * @task T706
9628
+ */
9629
+ stabilityScore: real("stability_score")
9562
9630
  },
9563
9631
  (table) => [
9564
9632
  primaryKey({ columns: [table.fromId, table.toId, table.edgeType] }),
9565
9633
  index("idx_brain_edges_from").on(table.fromId),
9566
9634
  index("idx_brain_edges_to").on(table.toId),
9567
- index("idx_brain_edges_type").on(table.edgeType)
9635
+ index("idx_brain_edges_type").on(table.edgeType),
9636
+ index("idx_brain_edges_last_reinforced").on(table.lastReinforcedAt),
9637
+ index("idx_brain_edges_plasticity_class").on(table.plasticityClass),
9638
+ index("idx_brain_edges_stability").on(table.stabilityScore)
9568
9639
  ]
9569
9640
  );
9570
9641
  brainRetrievalLog = sqliteTable(
@@ -9573,7 +9644,12 @@ var init_brain_schema = __esm({
9573
9644
  id: integer("id").primaryKey({ autoIncrement: true }),
9574
9645
  /** The search query or fetch IDs that triggered this retrieval. */
9575
9646
  query: text("query").notNull(),
9576
- /** Comma-separated list of entry IDs returned in this retrieval. */
9647
+ /**
9648
+ * JSON array of entry IDs returned in this retrieval.
9649
+ * Stored as JSON array string: '["obs:A","obs:B"]'.
9650
+ * Always write with JSON.stringify() — NEVER join(',').
9651
+ * Readers call JSON.parse(). Migration M1 converts any pre-existing CSV rows.
9652
+ */
9577
9653
  entryIds: text("entry_ids").notNull(),
9578
9654
  /** Number of entries returned. */
9579
9655
  entryCount: integer("entry_count").notNull(),
@@ -9583,12 +9659,26 @@ var init_brain_schema = __esm({
9583
9659
  tokensUsed: integer("tokens_used"),
9584
9660
  /** Session ID (soft FK to tasks.db sessions). Enables grouping retrievals by session for STDP analysis. */
9585
9661
  sessionId: text("session_id"),
9586
- createdAt: text("created_at").notNull().default(sql2`(datetime('now'))`)
9662
+ createdAt: text("created_at").notNull().default(sql2`(datetime('now'))`),
9663
+ // === T673-M1: STDP plasticity columns ===
9664
+ /** Sequence position of this retrieval within a batch query (0-based). */
9665
+ retrievalOrder: integer("retrieval_order"),
9666
+ /** Wall-clock ms since the previous retrieval row in the same batch. */
9667
+ deltaMs: integer("delta_ms"),
9668
+ /**
9669
+ * R-STDP reward signal: scalar [-1.0, +1.0], null = unlabeled.
9670
+ * Populated by backfillRewardSignals() at session end (Step 9a).
9671
+ * +1.0 = task verified and passed | +0.5 = done (unverified) | -0.5 = cancelled.
9672
+ * Per D-BRAIN-VIZ-13. backfillRewardSignals MUST skip rows where
9673
+ * session_id LIKE 'ses_backfill_%' (synthetic historical sessions, no task correlation).
9674
+ */
9675
+ rewardSignal: real("reward_signal")
9587
9676
  },
9588
9677
  (table) => [
9589
9678
  index("idx_retrieval_log_created").on(table.createdAt),
9590
9679
  index("idx_retrieval_log_source").on(table.source),
9591
- index("idx_retrieval_log_session").on(table.sessionId)
9680
+ index("idx_retrieval_log_session").on(table.sessionId),
9681
+ index("idx_retrieval_log_reward").on(table.rewardSignal)
9592
9682
  ]
9593
9683
  );
9594
9684
  brainPlasticityEvents = sqliteTable(
@@ -9612,14 +9702,182 @@ var init_brain_schema = __esm({
9612
9702
  /** ISO 8601 timestamp when this event was applied. */
9613
9703
  timestamp: text("timestamp").notNull().default(sql2`(datetime('now'))`),
9614
9704
  /** Session ID that triggered the STDP pass, if available. */
9615
- sessionId: text("session_id")
9705
+ sessionId: text("session_id"),
9706
+ // === T673-M2: Observability columns ===
9707
+ /**
9708
+ * Edge weight immediately BEFORE this plasticity event was applied.
9709
+ * Null on the first LTP event that inserts a new edge (edge didn't exist).
9710
+ * Enables "show learning history" in Studio viz without querying brain_weight_history.
9711
+ *
9712
+ * @task T696
9713
+ */
9714
+ weightBefore: real("weight_before"),
9715
+ /**
9716
+ * Edge weight immediately AFTER this plasticity event was applied.
9717
+ * Computed as CLAMP(weight_before + delta_w, 0.0, 1.0).
9718
+ * Redundant with delta_w but enables fast before/after display without arithmetic.
9719
+ *
9720
+ * @task T696
9721
+ */
9722
+ weightAfter: real("weight_after"),
9723
+ /**
9724
+ * Soft FK to brain_retrieval_log.id — the retrieval row that triggered this pair.
9725
+ * Null for externally-triggered or legacy events.
9726
+ * Enables: "which memory retrieval caused this edge to strengthen?"
9727
+ *
9728
+ * @task T696
9729
+ */
9730
+ retrievalLogId: integer("retrieval_log_id"),
9731
+ /**
9732
+ * R-STDP reward signal active when this event fired.
9733
+ * Copied from the retrieval_log row's reward_signal at time of plasticity pass.
9734
+ * Null = unmodulated. Denormalized for fast filtering without a JOIN.
9735
+ *
9736
+ * @task T696
9737
+ */
9738
+ rewardSignal: real("reward_signal"),
9739
+ /**
9740
+ * Wall-clock milliseconds between the two spikes that generated this event.
9741
+ * Pre-computed at INSERT time — avoids re-deriving from retrieval timestamps.
9742
+ * Enables analysis of STDP window distribution.
9743
+ *
9744
+ * @task T696
9745
+ */
9746
+ deltaTMs: integer("delta_t_ms")
9616
9747
  },
9617
9748
  (table) => [
9618
9749
  index("idx_plasticity_source").on(table.sourceNode),
9619
9750
  index("idx_plasticity_target").on(table.targetNode),
9620
9751
  index("idx_plasticity_timestamp").on(table.timestamp),
9621
9752
  index("idx_plasticity_session").on(table.sessionId),
9622
- index("idx_plasticity_kind").on(table.kind)
9753
+ index("idx_plasticity_kind").on(table.kind),
9754
+ index("idx_plasticity_retrieval_log").on(table.retrievalLogId),
9755
+ index("idx_plasticity_reward").on(table.rewardSignal)
9756
+ ]
9757
+ );
9758
+ brainWeightHistory = sqliteTable(
9759
+ "brain_weight_history",
9760
+ {
9761
+ id: integer("id").primaryKey({ autoIncrement: true }),
9762
+ /** from_id of the affected brain_page_edges row. */
9763
+ edgeFromId: text("edge_from_id").notNull(),
9764
+ /** to_id of the affected brain_page_edges row. */
9765
+ edgeToId: text("edge_to_id").notNull(),
9766
+ /** Edge type of the affected brain_page_edges row (e.g. 'co_retrieved'). */
9767
+ edgeType: text("edge_type").notNull(),
9768
+ /** Edge weight immediately before this event. Null if the edge was just created. */
9769
+ weightBefore: real("weight_before"),
9770
+ /** Edge weight after this event. CLAMP(weightBefore + deltaWeight, 0, 1). NOT NULL. */
9771
+ weightAfter: real("weight_after").notNull(),
9772
+ /**
9773
+ * Signed weight delta applied to the edge.
9774
+ * Positive = potentiation (LTP/Hebbian), negative = depression (LTD).
9775
+ * Prune events record the final weight that triggered deletion (negative).
9776
+ */
9777
+ deltaWeight: real("delta_weight").notNull(),
9778
+ /**
9779
+ * Plasticity event kind.
9780
+ * 'ltp' — Long-Term Potentiation (STDP pre-before-post)
9781
+ * 'ltd' — Long-Term Depression (STDP post-before-pre)
9782
+ * 'hebbian' — Co-retrieval Hebbian strengthening
9783
+ * 'decay' — Temporal decay (only prune-triggering decays written here)
9784
+ * 'prune' — Edge deleted (weight fell below min_weight threshold)
9785
+ * 'external' — Manually-applied external weight change
9786
+ */
9787
+ eventKind: text("event_kind").notNull(),
9788
+ /** Soft FK to brain_plasticity_events.id — the STDP event that caused this. */
9789
+ sourcePlasticityEventId: integer("source_plasticity_event_id"),
9790
+ /** Soft FK to brain_retrieval_log.id — the retrieval batch that triggered this. */
9791
+ retrievalLogId: integer("retrieval_log_id"),
9792
+ /** R-STDP reward signal at time of event (copied from retrieval_log.reward_signal). */
9793
+ rewardSignal: real("reward_signal"),
9794
+ /** ISO 8601 timestamp when this weight change was applied. */
9795
+ changedAt: text("changed_at").notNull().default(sql2`(datetime('now'))`)
9796
+ },
9797
+ (table) => [
9798
+ index("idx_weight_history_edge").on(table.edgeFromId, table.edgeToId, table.edgeType),
9799
+ index("idx_weight_history_from").on(table.edgeFromId),
9800
+ index("idx_weight_history_to").on(table.edgeToId),
9801
+ index("idx_weight_history_changed_at").on(table.changedAt),
9802
+ index("idx_weight_history_event_kind").on(table.eventKind),
9803
+ index("idx_weight_history_plasticity_event").on(table.sourcePlasticityEventId)
9804
+ ]
9805
+ );
9806
+ brainModulators = sqliteTable(
9807
+ "brain_modulators",
9808
+ {
9809
+ id: integer("id").primaryKey({ autoIncrement: true }),
9810
+ /**
9811
+ * Modulator event type. String (not enum constraint) for extensibility.
9812
+ * Expected values: 'task_verified'|'task_completed'|'task_cancelled'|
9813
+ * 'owner_verify'|'session_success'|'session_blocker'|'external'
9814
+ */
9815
+ modulatorType: text("modulator_type").notNull(),
9816
+ /**
9817
+ * Reward valence in range [-1.0, +1.0].
9818
+ * +1.0 = strong reward (verified correct task)
9819
+ * +0.5 = moderate reward (done, unverified)
9820
+ * -0.5 = mild correction (cancelled task)
9821
+ * -1.0 = strong correction (explicit invalidation)
9822
+ * 0.0 = neutral signal
9823
+ */
9824
+ valence: real("valence").notNull(),
9825
+ /**
9826
+ * Magnitude 0.0–1.0 confidence scaling.
9827
+ * Effective reward = valence × magnitude.
9828
+ * Defaults to 1.0 (full confidence).
9829
+ */
9830
+ magnitude: real("magnitude").notNull().default(1),
9831
+ /** Polymorphic source event ID — task ID, memory entry ID, or other string ref. */
9832
+ sourceEventId: text("source_event_id"),
9833
+ /** Session ID (soft FK to tasks.db sessions). */
9834
+ sessionId: text("session_id"),
9835
+ /** Human-readable description of why this modulator was emitted. */
9836
+ description: text("description"),
9837
+ /** ISO 8601 timestamp when this modulator event was recorded. */
9838
+ createdAt: text("created_at").notNull().default(sql2`(datetime('now'))`)
9839
+ },
9840
+ (table) => [
9841
+ index("idx_modulators_type").on(table.modulatorType),
9842
+ index("idx_modulators_session").on(table.sessionId),
9843
+ index("idx_modulators_created_at").on(table.createdAt),
9844
+ index("idx_modulators_source_event").on(table.sourceEventId),
9845
+ index("idx_modulators_valence").on(table.valence)
9846
+ ]
9847
+ );
9848
+ brainConsolidationEvents = sqliteTable(
9849
+ "brain_consolidation_events",
9850
+ {
9851
+ id: integer("id").primaryKey({ autoIncrement: true }),
9852
+ /**
9853
+ * What triggered this consolidation run. String (not enum constraint) for
9854
+ * forward compatibility with T628 scheduler.
9855
+ * Expected values: 'session_end' | 'maintenance' | 'scheduled' | 'manual'
9856
+ */
9857
+ trigger: text("trigger").notNull(),
9858
+ /** Session ID that initiated this consolidation (soft FK to tasks.db sessions). */
9859
+ sessionId: text("session_id"),
9860
+ /**
9861
+ * JSON-serialized ConsolidationResult — all per-step counts and metrics.
9862
+ * Shape: { [stepName: string]: { count: number, durationMs?: number } }
9863
+ * Required NOT NULL — every run must record its results for T628 scheduling.
9864
+ */
9865
+ stepResultsJson: text("step_results_json").notNull(),
9866
+ /** Wall-clock milliseconds from start to completion. Null if run did not complete. */
9867
+ durationMs: integer("duration_ms"),
9868
+ /**
9869
+ * Whether the run succeeded.
9870
+ * Stored as integer(boolean) per Drizzle SQLite boolean convention.
9871
+ * true = completed without unhandled error, false = partial or error.
9872
+ */
9873
+ succeeded: integer("succeeded", { mode: "boolean" }).notNull().default(true),
9874
+ /** ISO 8601 timestamp when this consolidation run started. */
9875
+ startedAt: text("started_at").notNull().default(sql2`(datetime('now'))`)
9876
+ },
9877
+ (table) => [
9878
+ index("idx_consolidation_events_started_at").on(table.startedAt),
9879
+ index("idx_consolidation_events_trigger").on(table.trigger),
9880
+ index("idx_consolidation_events_session").on(table.sessionId)
9623
9881
  ]
9624
9882
  );
9625
9883
  }
@@ -14490,6 +14748,108 @@ function runBrainMigrations(nativeDb, db) {
14490
14748
  AND provenance LIKE 'consolidation:%'`
14491
14749
  ).run();
14492
14750
  }
14751
+ if (tableExists(nativeDb, "brain_retrieval_log")) {
14752
+ ensureColumns(
14753
+ nativeDb,
14754
+ "brain_retrieval_log",
14755
+ [
14756
+ { name: "session_id", ddl: "text" },
14757
+ { name: "reward_signal", ddl: "real" },
14758
+ { name: "retrieval_order", ddl: "integer" },
14759
+ { name: "delta_ms", ddl: "integer" }
14760
+ ],
14761
+ "brain"
14762
+ );
14763
+ }
14764
+ if (tableExists(nativeDb, "brain_plasticity_events")) {
14765
+ ensureColumns(
14766
+ nativeDb,
14767
+ "brain_plasticity_events",
14768
+ [
14769
+ { name: "session_id", ddl: "text" },
14770
+ { name: "weight_before", ddl: "real" },
14771
+ { name: "weight_after", ddl: "real" },
14772
+ { name: "retrieval_log_id", ddl: "integer" },
14773
+ { name: "reward_signal", ddl: "real" },
14774
+ { name: "delta_t_ms", ddl: "integer" }
14775
+ ],
14776
+ "brain"
14777
+ );
14778
+ }
14779
+ ensureColumns(
14780
+ nativeDb,
14781
+ "brain_page_edges",
14782
+ [
14783
+ { name: "last_reinforced_at", ddl: "text" },
14784
+ { name: "reinforcement_count", ddl: "integer NOT NULL DEFAULT 0" },
14785
+ { name: "plasticity_class", ddl: "text NOT NULL DEFAULT 'static'" },
14786
+ { name: "last_depressed_at", ddl: "text" },
14787
+ { name: "depression_count", ddl: "integer NOT NULL DEFAULT 0" },
14788
+ { name: "stability_score", ddl: "real" }
14789
+ ],
14790
+ "brain"
14791
+ );
14792
+ if (tableExists(nativeDb, "brain_page_edges")) {
14793
+ nativeDb.prepare(
14794
+ `UPDATE brain_page_edges SET plasticity_class = 'hebbian'
14795
+ WHERE edge_type = 'co_retrieved' AND plasticity_class = 'static'`
14796
+ ).run();
14797
+ }
14798
+ nativeDb.exec(
14799
+ `CREATE TABLE IF NOT EXISTS brain_weight_history (
14800
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14801
+ edge_from_id TEXT NOT NULL,
14802
+ edge_to_id TEXT NOT NULL,
14803
+ edge_type TEXT NOT NULL,
14804
+ weight_before REAL,
14805
+ weight_after REAL NOT NULL,
14806
+ delta_weight REAL NOT NULL,
14807
+ event_kind TEXT NOT NULL,
14808
+ source_plasticity_event_id INTEGER,
14809
+ retrieval_log_id INTEGER,
14810
+ reward_signal REAL,
14811
+ changed_at TEXT NOT NULL DEFAULT (datetime('now'))
14812
+ )`
14813
+ );
14814
+ nativeDb.exec(
14815
+ `CREATE INDEX IF NOT EXISTS idx_weight_history_edge
14816
+ ON brain_weight_history (edge_from_id, edge_to_id, edge_type)`
14817
+ );
14818
+ nativeDb.exec(
14819
+ `CREATE INDEX IF NOT EXISTS idx_weight_history_changed_at
14820
+ ON brain_weight_history (changed_at)`
14821
+ );
14822
+ nativeDb.exec(
14823
+ `CREATE TABLE IF NOT EXISTS brain_modulators (
14824
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14825
+ modulator_type TEXT NOT NULL,
14826
+ valence REAL NOT NULL,
14827
+ magnitude REAL NOT NULL DEFAULT 1.0,
14828
+ source_event_id TEXT,
14829
+ session_id TEXT,
14830
+ description TEXT,
14831
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
14832
+ )`
14833
+ );
14834
+ nativeDb.exec(
14835
+ `CREATE INDEX IF NOT EXISTS idx_modulators_session
14836
+ ON brain_modulators (session_id)`
14837
+ );
14838
+ nativeDb.exec(
14839
+ `CREATE TABLE IF NOT EXISTS brain_consolidation_events (
14840
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
14841
+ trigger TEXT NOT NULL,
14842
+ session_id TEXT,
14843
+ step_results_json TEXT NOT NULL,
14844
+ duration_ms INTEGER,
14845
+ succeeded INTEGER NOT NULL DEFAULT 1,
14846
+ started_at TEXT NOT NULL DEFAULT (datetime('now'))
14847
+ )`
14848
+ );
14849
+ nativeDb.exec(
14850
+ `CREATE INDEX IF NOT EXISTS idx_consolidation_events_started_at
14851
+ ON brain_consolidation_events (started_at)`
14852
+ );
14493
14853
  }
14494
14854
  function loadBrainVecExtension(nativeDb) {
14495
14855
  try {
@@ -16653,13 +17013,48 @@ var init_link_pipeline = __esm({
16653
17013
  var brain_accessor_exports = {};
16654
17014
  __export(brain_accessor_exports, {
16655
17015
  BrainDataAccessor: () => BrainDataAccessor,
16656
- getBrainAccessor: () => getBrainAccessor
17016
+ getBrainAccessor: () => getBrainAccessor,
17017
+ insertModulatorRow: () => insertModulatorRow,
17018
+ insertWeightHistoryRow: () => insertWeightHistoryRow,
17019
+ logConsolidationComplete: () => logConsolidationComplete,
17020
+ logConsolidationStart: () => logConsolidationStart
16657
17021
  });
16658
17022
  import { and as and5, asc as asc2, desc as desc2, eq as eq7, gte as gte2, or as or4 } from "drizzle-orm";
16659
17023
  async function getBrainAccessor(cwd) {
16660
17024
  const db = await getBrainDb(cwd);
16661
17025
  return new BrainDataAccessor(db);
16662
17026
  }
17027
+ async function insertWeightHistoryRow(cwd, input) {
17028
+ const db = await getBrainDb(cwd);
17029
+ const result = await db.insert(brainWeightHistory).values(input).returning();
17030
+ return result[0];
17031
+ }
17032
+ async function insertModulatorRow(cwd, input) {
17033
+ const db = await getBrainDb(cwd);
17034
+ const result = await db.insert(brainModulators).values(input).returning();
17035
+ return result[0];
17036
+ }
17037
+ async function logConsolidationStart(cwd, trigger, sessionId) {
17038
+ const db = await getBrainDb(cwd);
17039
+ const result = await db.insert(brainConsolidationEvents).values({
17040
+ trigger,
17041
+ sessionId: sessionId ?? null,
17042
+ // stepResultsJson is required NOT NULL — use empty object as placeholder
17043
+ // until logConsolidationComplete updates it with final step results.
17044
+ stepResultsJson: "{}",
17045
+ succeeded: true
17046
+ }).returning({ id: brainConsolidationEvents.id });
17047
+ return result[0].id;
17048
+ }
17049
+ async function logConsolidationComplete(cwd, id, stats2, durationMs, succeeded = true) {
17050
+ const db = await getBrainDb(cwd);
17051
+ const result = await db.update(brainConsolidationEvents).set({
17052
+ stepResultsJson: JSON.stringify(stats2),
17053
+ durationMs,
17054
+ succeeded
17055
+ }).where(eq7(brainConsolidationEvents.id, id)).returning();
17056
+ return result[0];
17057
+ }
16663
17058
  var BrainDataAccessor;
16664
17059
  var init_brain_accessor = __esm({
16665
17060
  "packages/core/src/store/brain-accessor.ts"() {
@@ -48852,7 +49247,7 @@ async function logRetrieval(projectRoot, query, entryIds, source, tokensUsed, se
48852
49247
  "INSERT INTO brain_retrieval_log (query, entry_ids, entry_count, source, tokens_used, session_id) VALUES (?, ?, ?, ?, ?, ?)"
48853
49248
  ).run(
48854
49249
  query,
48855
- entryIds.join(","),
49250
+ JSON.stringify(entryIds),
48856
49251
  entryIds.length,
48857
49252
  source,
48858
49253
  tokensUsed ?? null,
@@ -77860,6 +78255,85 @@ function checkLegacyAgentOutputs(projectRoot) {
77860
78255
  fix: null
77861
78256
  };
77862
78257
  }
78258
+ function checkCanonicalRcasdPaths(projectRoot) {
78259
+ const root = projectRoot ?? process.cwd();
78260
+ const cleoDir = join91(root, ".cleo");
78261
+ const failures = [];
78262
+ const deprecatedDirs = ["research", "consensus", "specs", "decomposition"];
78263
+ for (const dir of deprecatedDirs) {
78264
+ const dirPath = join91(cleoDir, dir);
78265
+ if (existsSync89(dirPath)) {
78266
+ try {
78267
+ const entries = __require("node:fs").readdirSync(dirPath).filter(
78268
+ (e) => !e.startsWith(".")
78269
+ );
78270
+ if (entries.length > 0) {
78271
+ failures.push(
78272
+ `deprecated .cleo/${dir}/ contains files (should migrate to .cleo/rcasd/{epicId}/${dir}/)`
78273
+ );
78274
+ }
78275
+ } catch {
78276
+ }
78277
+ }
78278
+ }
78279
+ const rcasdPath = join91(cleoDir, "rcasd");
78280
+ if (existsSync89(rcasdPath)) {
78281
+ try {
78282
+ const rootFiles = __require("node:fs").readdirSync(rcasdPath).filter(
78283
+ (e) => e.endsWith(".md")
78284
+ );
78285
+ if (rootFiles.length > 0) {
78286
+ failures.push(
78287
+ `misplaced .md files in .cleo/rcasd/ root (audit-*.md, etc. should be in .cleo/agent-outputs/)`
78288
+ );
78289
+ }
78290
+ } catch {
78291
+ }
78292
+ }
78293
+ const claudedocsPath = join91(root, "claudedocs");
78294
+ if (existsSync89(claudedocsPath)) {
78295
+ try {
78296
+ const agentOutputs = join91(claudedocsPath, "agent-outputs");
78297
+ if (existsSync89(agentOutputs)) {
78298
+ failures.push(
78299
+ `legacy claudedocs/agent-outputs/ directory exists (should migrate to .cleo/agent-outputs/)`
78300
+ );
78301
+ }
78302
+ } catch {
78303
+ }
78304
+ }
78305
+ if (failures.length > 0) {
78306
+ return {
78307
+ id: "canonical_rcasd_paths",
78308
+ category: "configuration",
78309
+ status: "warning",
78310
+ message: `Canonical path drift detected (ADR-045): ${failures.join("; ")}`,
78311
+ details: {
78312
+ issues: failures,
78313
+ canonical: {
78314
+ rcasdStages: ".cleo/rcasd/{epicId}/{stage}/{epicId}-{stage}.md",
78315
+ agentOutputs: ".cleo/agent-outputs/{taskId}-{slug}.md",
78316
+ publishedSpecs: "docs/specs/SPEC-NAME.md"
78317
+ }
78318
+ },
78319
+ fix: "cleo upgrade (migrates old paths) or manually move files per ADR-045"
78320
+ };
78321
+ }
78322
+ return {
78323
+ id: "canonical_rcasd_paths",
78324
+ category: "configuration",
78325
+ status: "passed",
78326
+ message: "All artifacts at canonical RCASD paths (ADR-045 compliant)",
78327
+ details: {
78328
+ canonical: {
78329
+ rcasdStages: ".cleo/rcasd/{epicId}/{stage}/{epicId}-{stage}.md",
78330
+ agentOutputs: ".cleo/agent-outputs/{taskId}-{slug}.md",
78331
+ publishedSpecs: "docs/specs/SPEC-NAME.md"
78332
+ }
78333
+ },
78334
+ fix: null
78335
+ };
78336
+ }
77863
78337
  function checkCaampMarkerIntegrity(projectRoot) {
77864
78338
  const root = projectRoot ?? process.cwd();
77865
78339
  const files = ["CLAUDE.md", "AGENTS.md"];
@@ -78172,6 +78646,8 @@ function runAllGlobalChecks(cleoHome, projectRoot) {
78172
78646
  checkCoreFilesNotIgnored(projectRoot),
78173
78647
  checkSqliteNotTracked(projectRoot),
78174
78648
  checkLegacyAgentOutputs(projectRoot),
78649
+ // ADR-045 canonical paths check (T708)
78650
+ checkCanonicalRcasdPaths(projectRoot),
78175
78651
  // Injection chain checks (T5153)
78176
78652
  checkCaampMarkerIntegrity(projectRoot),
78177
78653
  checkAtReferenceTargetExists(projectRoot),
@@ -78732,6 +79208,7 @@ async function coreDoctorReport(projectRoot) {
78732
79208
  checks.push(mapCheckResult(checkVitalFilesTracked(projectRoot)));
78733
79209
  checks.push(mapCheckResult(checkCoreFilesNotIgnored(projectRoot)));
78734
79210
  checks.push(mapCheckResult(checkLegacyAgentOutputs(projectRoot)));
79211
+ checks.push(mapCheckResult(checkCanonicalRcasdPaths(projectRoot)));
78735
79212
  const cleoGitHeadExists = existsSync90(join92(cleoDir, ".git", "HEAD"));
78736
79213
  checks.push({
78737
79214
  check: "cleo_git_repo",