@smartmemory/compose 0.3.5-beta → 0.3.7

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 (138) hide show
  1. package/.claude/skills/compose/SKILL.md +42 -88
  2. package/bin/compose.js +288 -0
  3. package/bin/git-hooks/pre-push.template +29 -0
  4. package/bin/judgment-import.js +572 -0
  5. package/contracts/feature-json.schema.json +5 -0
  6. package/contracts/judgment-record.schema.json +821 -0
  7. package/dist/assets/App-PkZzHeMj.js +894 -0
  8. package/dist/assets/_baseUniq-Bo837sRJ.js +1 -0
  9. package/dist/assets/arc-BafGpyqE.js +1 -0
  10. package/dist/assets/architectureDiagram-Q4EWVU46-BOBfUsqL.js +36 -0
  11. package/dist/assets/blockDiagram-DXYQGD6D-Dwodev1a.js +132 -0
  12. package/dist/assets/{browser-BSM23If2.js → browser-1ntj1-x_.js} +6 -6
  13. package/dist/assets/{c4Diagram-LMCZKHZV-CUmTCrMW.js → c4Diagram-AHTNJAMY-CU_bhYag.js} +1 -1
  14. package/dist/assets/channel-qVK_qn4E.js +1 -0
  15. package/dist/assets/{chunk-JWPE2WC7-DQFs7gjy.js → chunk-4BX2VUAB-p8WsDwnO.js} +1 -1
  16. package/dist/assets/chunk-4TB4RGXK-B8h7-eR0.js +206 -0
  17. package/dist/assets/{chunk-XXDRQBXY-CSdt06Hp.js → chunk-55IACEB6-DxeEr98s.js} +1 -1
  18. package/dist/assets/{chunk-VR4S4FIN-Dvhgnm7E.js → chunk-EDXVE4YY-BYt8F151.js} +1 -1
  19. package/dist/assets/{chunk-5VM5RSS4-Qw-L7OoJ.js → chunk-FMBD7UC4-DGSOVeie.js} +1 -1
  20. package/dist/assets/chunk-OYMX7WX6-B-QdgYR2.js +231 -0
  21. package/dist/assets/{chunk-2Q5K7J3B-Cyvi1AFH.js → chunk-QZHKN3VN-Du5UAZLs.js} +1 -1
  22. package/dist/assets/{chunk-32BRIVSS-B1y5FpOd.js → chunk-YZCP3GAM-C8JbNBSk.js} +1 -1
  23. package/dist/assets/classDiagram-6PBFFD2Q-B8UcfC1q.js +1 -0
  24. package/dist/assets/classDiagram-v2-HSJHXN6E-B8UcfC1q.js +1 -0
  25. package/dist/assets/clone-Pu3RyLUh.js +1 -0
  26. package/dist/assets/{cose-bilkent-JH36ORCC-G8l05tXy.js → cose-bilkent-S5V4N54A-O1ESaqge.js} +1 -1
  27. package/dist/assets/dagre-KV5264BT-CPTmFPHw.js +4 -0
  28. package/dist/assets/diagram-5BDNPKRD-B3PNrWs5.js +10 -0
  29. package/dist/assets/diagram-G4DWMVQ6-Cscfr6vc.js +24 -0
  30. package/dist/assets/diagram-MMDJMWI5-CSfqZ-TM.js +43 -0
  31. package/dist/assets/diagram-TYMM5635-Cg4aYS7W.js +24 -0
  32. package/dist/assets/erDiagram-SMLLAGMA-_ZqwG5pl.js +85 -0
  33. package/dist/assets/flowDiagram-DWJPFMVM-C83boxFT.js +162 -0
  34. package/dist/assets/ganttDiagram-T4ZO3ILL-CWnIjuEi.js +292 -0
  35. package/dist/assets/gitGraphDiagram-UUTBAWPF-DrMdxZfH.js +106 -0
  36. package/dist/assets/graph-Bi99_6Yf.js +331 -0
  37. package/dist/assets/graph-RE4I7Ty7.js +1 -0
  38. package/dist/assets/index-Rm2RE-c0.js +123 -0
  39. package/dist/assets/infoDiagram-42DDH7IO-BLmP4Epr.js +2 -0
  40. package/dist/assets/{ishikawaDiagram-FXEZZL3T-Hm4zSDlM.js → ishikawaDiagram-UXIWVN3A-yuWWshKN.js} +5 -5
  41. package/dist/assets/{journeyDiagram-5HDEW3XC-CIqcTFuO.js → journeyDiagram-VCZTEJTY-BOfhaJov.js} +1 -1
  42. package/dist/assets/{kanban-definition-HUTT4EX6-CB9jCXj_.js → kanban-definition-6JOO6SKY-Bbolde15.js} +7 -7
  43. package/dist/assets/katex-DkKDou_j.js +257 -0
  44. package/dist/assets/layout-BSf33zm8.js +1 -0
  45. package/dist/assets/{linear-CsXqv70E.js → linear-AvSTWMqx.js} +1 -1
  46. package/dist/assets/min-QBM8H4xN.js +1 -0
  47. package/dist/assets/{mindmap-definition-LN4V7U3C-CpqH5F_P.js → mindmap-definition-QFDTVHPH-BuvgtqIc.js} +7 -7
  48. package/dist/assets/{mobile-CaoXUwAr.js → mobile-BnXEOE3U.js} +2 -2
  49. package/dist/assets/pieDiagram-DEJITSTG-DIzF16vh.js +30 -0
  50. package/dist/assets/quadrantDiagram-34T5L4WZ-D-mbUIjS.js +7 -0
  51. package/dist/assets/{requirementDiagram-TGXJPOKE-D1lG1P7h.js → requirementDiagram-MS252O5E-CEs4kCLd.js} +3 -3
  52. package/dist/assets/sankeyDiagram-XADWPNL6-DFsnCr9n.js +10 -0
  53. package/dist/assets/sequenceDiagram-FGHM5R23-BEJYdTjQ.js +157 -0
  54. package/dist/assets/stateDiagram-FHFEXIEX-BBXs57uY.js +1 -0
  55. package/dist/assets/stateDiagram-v2-QKLJ7IA2-BqKuX4rj.js +1 -0
  56. package/dist/assets/{timeline-definition-FHXFAJF6-CzqqUDvt.js → timeline-definition-GMOUNBTQ-BGvLoVAY.js} +3 -3
  57. package/dist/assets/vennDiagram-DHZGUBPP-9LaBTMe0.js +34 -0
  58. package/dist/assets/wardley-RL74JXVD-P4MEqMTP.js +162 -0
  59. package/dist/assets/wardleyDiagram-NUSXRM2D-o-tmxnlC.js +20 -0
  60. package/dist/assets/xychartDiagram-5P7HB3ND-Dpn7V6qk.js +7 -0
  61. package/dist/index.html +2 -2
  62. package/lib/bug-escalation.js +30 -4
  63. package/lib/build.js +777 -52
  64. package/lib/canon-guard.js +223 -0
  65. package/lib/canon-registry.js +187 -0
  66. package/lib/codex-preflight.js +26 -4
  67. package/lib/dispatch-ledger.js +301 -0
  68. package/lib/dispatch-metrics.js +236 -0
  69. package/lib/experiment-judge.js +6 -1
  70. package/lib/feature-writer.js +9 -0
  71. package/lib/gsd.js +11 -2
  72. package/lib/hooks-status.js +32 -3
  73. package/lib/idempotency.js +33 -8
  74. package/lib/judgment/schema.js +23 -0
  75. package/lib/judgment/store/index.js +207 -0
  76. package/lib/judgment/store/records.js +405 -0
  77. package/lib/judgment-attest.js +259 -0
  78. package/lib/judgment-gen.js +691 -0
  79. package/lib/judgment-verify.js +153 -0
  80. package/lib/judgment-write-guard.js +230 -0
  81. package/lib/judgment-writer.js +3403 -0
  82. package/lib/lane-gate.js +2 -0
  83. package/lib/lifecycle-modes.js +42 -0
  84. package/lib/local-claude-connector.js +199 -54
  85. package/lib/mcp-enforcement.js +21 -35
  86. package/lib/result-normalizer.js +93 -15
  87. package/lib/review-normalize.js +4 -0
  88. package/lib/stratum-mcp-client.js +131 -6
  89. package/package.json +2 -2
  90. package/server/compose-mcp-tools.js +49 -1
  91. package/server/compose-mcp.js +220 -0
  92. package/server/mcp-tool-policy.js +1 -0
  93. package/dist/assets/App-BG92y_CY.js +0 -896
  94. package/dist/assets/abnfDiagram-VRR7QNED-Bip6cMjP.js +0 -1
  95. package/dist/assets/arc-CyMTM07c.js +0 -1
  96. package/dist/assets/architectureDiagram-ZJ3FMSHR-BukV0CJw.js +0 -36
  97. package/dist/assets/blockDiagram-677ZJIJ3-Bjb_MNB4.js +0 -132
  98. package/dist/assets/channel-BzkmtaLy.js +0 -1
  99. package/dist/assets/chunk-EX3LRPZG-G_MZEji_.js +0 -231
  100. package/dist/assets/chunk-MOJQB5TN-CQZQYMcz.js +0 -88
  101. package/dist/assets/chunk-RYQCIY6F-CmAda4sZ.js +0 -1
  102. package/dist/assets/chunk-V7JOEXUC-CbjBz-Ek.js +0 -206
  103. package/dist/assets/classDiagram-OUVF2IWQ-BIvLcbex.js +0 -1
  104. package/dist/assets/classDiagram-v2-EOCWNBFH-BIvLcbex.js +0 -1
  105. package/dist/assets/cynefin-VYW2F7L2-C3n1PNJC.js +0 -178
  106. package/dist/assets/cynefinDiagram-TSTJHNR4-C8N_CvKJ.js +0 -62
  107. package/dist/assets/dagre-VKFMJZFB-B2pkwDhN.js +0 -4
  108. package/dist/assets/diagram-FQU43EPY-DPHD_t-1.js +0 -3
  109. package/dist/assets/diagram-G47NLZAW-DGmHIZRr.js +0 -24
  110. package/dist/assets/diagram-NH7WQ7WH-BlLWwKVt.js +0 -24
  111. package/dist/assets/diagram-OA4YK3LP-DEv704Re.js +0 -30
  112. package/dist/assets/diagram-WEI45ONY-Bj6PGUCe.js +0 -41
  113. package/dist/assets/ebnfDiagram-CCIWWBDH-Ch-icksT.js +0 -1
  114. package/dist/assets/erDiagram-Q63AITRT-CI3lwEFA.js +0 -85
  115. package/dist/assets/flowDiagram-23GEKE2U-DcUANpAz.js +0 -156
  116. package/dist/assets/ganttDiagram-NO4QXBWP-Cy0Ie8KS.js +0 -292
  117. package/dist/assets/gitGraphDiagram-IHSO6WYX-BswI52az.js +0 -106
  118. package/dist/assets/graph-BXPQrYYB.js +0 -331
  119. package/dist/assets/graph-C9eacEi8.js +0 -1
  120. package/dist/assets/index-B1H7-G2y.js +0 -119
  121. package/dist/assets/infoDiagram-FWYZ7A6U-BBunqvr-.js +0 -2
  122. package/dist/assets/katex-C5jXJg4s.js +0 -257
  123. package/dist/assets/layout-DEXfKzaS.js +0 -1
  124. package/dist/assets/map-Czzmt4hB.js +0 -1
  125. package/dist/assets/pegDiagram-2B236MQR-HpQyrDcm.js +0 -1
  126. package/dist/assets/pieDiagram-ENE6RG2P-xkcpHwnY.js +0 -39
  127. package/dist/assets/quadrantDiagram-ABIIQ3AL-kebZIOmu.js +0 -7
  128. package/dist/assets/railroadDiagram-RFXS5EU6-DNvGwWm9.js +0 -1
  129. package/dist/assets/sankeyDiagram-HTMAVEWB-DOEEcn52.js +0 -40
  130. package/dist/assets/sequenceDiagram-DBY2YBRQ-DynIVCk_.js +0 -162
  131. package/dist/assets/sizeCapture-X5ZJPWSS-DJXFjUUL.js +0 -1
  132. package/dist/assets/stateDiagram-2N3HPSRC-OZ7trFh5.js +0 -1
  133. package/dist/assets/stateDiagram-v2-6OUMAXLB-GcPRHAJT.js +0 -1
  134. package/dist/assets/swimlanes-5IMT3BWC-DULYAFmB.js +0 -2
  135. package/dist/assets/swimlanesDiagram-G3AALYLV-BB0gCy01.js +0 -8
  136. package/dist/assets/vennDiagram-L72KCM5P-CdKmtcFL.js +0 -34
  137. package/dist/assets/wardleyDiagram-EHGQE667-DknGwjCA.js +0 -78
  138. package/dist/assets/xychartDiagram-FW5EYKEG-DdYyKfQ3.js +0 -7
@@ -19,10 +19,49 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
19
19
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
20
20
  import YAML from 'yaml';
21
21
  import { validateBuildStreamEvent } from './build-stream-schema.js';
22
+ import { appendEvent, resolveDispatchLedgerCwd } from './dispatch-ledger.js';
22
23
  import { resolveStratumMcpConnection } from './stratum-engine.js';
23
24
 
24
25
  const RUNTIME_INPUT_REF = /^\$\.input\.([A-Za-z_][A-Za-z0-9_]*)$/;
25
26
 
27
+ function finiteOrNull(value) {
28
+ return typeof value === 'number' && Number.isFinite(value) ? value : null;
29
+ }
30
+
31
+ function nonemptyStringOrNull(value) {
32
+ return typeof value === 'string' && value.length > 0 ? value : null;
33
+ }
34
+
35
+ function isBlockedDispatch(value) {
36
+ const candidates = [
37
+ value?.status,
38
+ value?.outcome,
39
+ value?.code,
40
+ value?.error?.status,
41
+ value?.error?.code,
42
+ ];
43
+ return candidates.some((candidate) => {
44
+ const normalized = typeof candidate === 'string' ? candidate.toLowerCase() : '';
45
+ return normalized === 'blocked'
46
+ || normalized === 'budget_exhausted'
47
+ || normalized === 'budget-exhausted';
48
+ });
49
+ }
50
+
51
+ function attachDispatchId(value, dispatchId) {
52
+ if ((typeof value !== 'object' && typeof value !== 'function') || value === null) return;
53
+ try {
54
+ Object.defineProperty(value, 'dispatchId', {
55
+ configurable: true,
56
+ enumerable: false,
57
+ value: dispatchId,
58
+ });
59
+ } catch {
60
+ // A frozen third-party value cannot carry metadata. Dispatch behavior still
61
+ // wins over observability.
62
+ }
63
+ }
64
+
26
65
  /**
27
66
  * Resolve producer-owned authoring values that TS v1 deliberately requires to
28
67
  * be literals. Compose knows these values at plan time, so mutate a cloned spec
@@ -147,6 +186,86 @@ export class StratumMcpClient {
147
186
  // STRAT-PAR-STREAM: subscribers keyed by `${flowId}::${stepId}` → Set<handler>
148
187
  #eventSubs = new Map();
149
188
 
189
+ #recordAgentDispatch(dispatchId, agentType, opts, resultOrError, outcome, elapsedMs) {
190
+ try {
191
+ const context = opts.telemetry && typeof opts.telemetry === 'object'
192
+ ? opts.telemetry
193
+ : {};
194
+ const returnedTelemetry = resultOrError?.telemetry
195
+ && typeof resultOrError.telemetry === 'object'
196
+ ? resultOrError.telemetry
197
+ : {};
198
+ const usage = resultOrError?.usage && typeof resultOrError.usage === 'object'
199
+ ? resultOrError.usage
200
+ : {};
201
+ const effortIntended = Object.hasOwn(context, 'effort_intended')
202
+ ? nonemptyStringOrNull(context.effort_intended)
203
+ : nonemptyStringOrNull(opts.effort);
204
+ const event = {
205
+ kind: 'dispatch',
206
+ dispatch_id: dispatchId,
207
+ site: nonemptyStringOrNull(context.site) ?? 'unattributed',
208
+ agent: String(agentType ?? 'claude').split(':', 1)[0] || 'claude',
209
+ outcome,
210
+ model: nonemptyStringOrNull(returnedTelemetry.model)
211
+ ?? nonemptyStringOrNull(usage.model),
212
+ effort_intended: effortIntended,
213
+ effort_executed: nonemptyStringOrNull(returnedTelemetry.effort),
214
+ tokens_in: null,
215
+ tokens_out: null,
216
+ tokens_total: finiteOrNull(usage.tokens),
217
+ usd: finiteOrNull(usage.usd),
218
+ duration_ms: finiteOrNull(usage.ms)
219
+ ?? finiteOrNull(returnedTelemetry.durationMs)
220
+ ?? finiteOrNull(elapsedMs),
221
+ };
222
+ for (const [field, value] of [
223
+ ['build_id', context.build_id],
224
+ ['feature_code', context.feature_code],
225
+ ['step_id', context.step_id],
226
+ ]) {
227
+ if (nonemptyStringOrNull(value) !== null) event[field] = value;
228
+ }
229
+ if (finiteOrNull(context.attempt) !== null) event.attempt = context.attempt;
230
+ appendEvent(resolveDispatchLedgerCwd(context.project_cwd), event);
231
+ } catch {
232
+ // Dispatch capture is fail-open by contract.
233
+ }
234
+ }
235
+
236
+ async #dispatchAgentRun(agentType, prompt, opts, callOpts) {
237
+ const dispatchId = randomUUID();
238
+ const startedAt = Date.now();
239
+ try {
240
+ const result = await this.#callTool(
241
+ 'stratum_agent_run',
242
+ buildAgentRunRequest(agentType, prompt, opts),
243
+ callOpts,
244
+ );
245
+ this.#recordAgentDispatch(
246
+ dispatchId,
247
+ agentType,
248
+ opts,
249
+ result,
250
+ isBlockedDispatch(result) ? 'blocked' : 'ok',
251
+ Date.now() - startedAt,
252
+ );
253
+ attachDispatchId(result, dispatchId);
254
+ return result;
255
+ } catch (error) {
256
+ this.#recordAgentDispatch(
257
+ dispatchId,
258
+ agentType,
259
+ opts,
260
+ error,
261
+ isBlockedDispatch(error) ? 'blocked' : 'error',
262
+ Date.now() - startedAt,
263
+ );
264
+ attachDispatchId(error, dispatchId);
265
+ throw error;
266
+ }
267
+ }
268
+
150
269
  /**
151
270
  * Subscribe to BuildStreamEvent push notifications scoped to a (flowId, stepId).
152
271
  * Handler receives a parsed BuildStreamEvent envelope. Returns an unsubscribe fn.
@@ -515,9 +634,12 @@ export class StratumMcpClient {
515
634
  // those knobs are resolved compose-side (resolveAgentConfig) and applied at
516
635
  // this seam — the engine's connector honors model + sandboxMode. `cwd` is
517
636
  // required, so default to the process cwd when the caller omits it.
518
- return this.#callTool('stratum_agent_run',
519
- buildAgentRunRequest(agentType, prompt, opts),
520
- { subscribeProgress: true, correlationId });
637
+ return this.#dispatchAgentRun(
638
+ agentType,
639
+ prompt,
640
+ opts,
641
+ { subscribeProgress: true, correlationId },
642
+ );
521
643
  }
522
644
 
523
645
  /**
@@ -531,9 +653,12 @@ export class StratumMcpClient {
531
653
  * @returns {Promise<string>}
532
654
  */
533
655
  async runAgentText(agentType, prompt, opts = {}) {
534
- const result = await this.#callTool('stratum_agent_run',
535
- buildAgentRunRequest(agentType, prompt, opts),
536
- { subscribeProgress: false });
656
+ const result = await this.#dispatchAgentRun(
657
+ agentType,
658
+ prompt,
659
+ opts,
660
+ { subscribeProgress: false },
661
+ );
537
662
  return result?.text ?? '';
538
663
  }
539
664
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartmemory/compose",
3
- "version": "0.3.5-beta",
3
+ "version": "0.3.7",
4
4
  "description": "Structured AI dev pipeline — goal-to-product orchestration with gates, iteration loops, and feature lifecycle management.",
5
5
  "author": "SmartMemory",
6
6
  "license": "MIT",
@@ -85,7 +85,7 @@
85
85
  "@radix-ui/react-toggle": "^1.1.10",
86
86
  "@radix-ui/react-toggle-group": "^1.1.11",
87
87
  "@radix-ui/react-tooltip": "^1.2.8",
88
- "@smartmemory/stratum": "0.3.2",
88
+ "@smartmemory/stratum": "^0.3.3",
89
89
  "@tanstack/react-virtual": "^3.13.23",
90
90
  "ajv": "^8.18.0",
91
91
  "ajv-formats": "^3.0.1",
@@ -806,6 +806,55 @@ export function toolGetWorkspace() {
806
806
 
807
807
  export function _getBinding() { return _binding?.id ?? null; }
808
808
 
809
+ // ---------------------------------------------------------------------------
810
+ // Judgment writer — COMP-JUDGMENT-WRITER (S06)
811
+ // ---------------------------------------------------------------------------
812
+
813
+ export async function toolJudgmentPositionCreate(args) {
814
+ const { judgmentPositionCreate } = await import('../lib/judgment-writer.js');
815
+ return judgmentPositionCreate(getTargetRoot(), args);
816
+ }
817
+
818
+ export async function toolJudgmentPositionAmend(args) {
819
+ const { judgmentPositionAmend } = await import('../lib/judgment-writer.js');
820
+ return judgmentPositionAmend(getTargetRoot(), args);
821
+ }
822
+
823
+ export async function toolJudgmentJointAdd(args) {
824
+ const { judgmentJointAdd } = await import('../lib/judgment-writer.js');
825
+ return judgmentJointAdd(getTargetRoot(), args);
826
+ }
827
+
828
+ export async function toolJudgmentTransition(args) {
829
+ const { judgmentTransition } = await import('../lib/judgment-writer.js');
830
+ return judgmentTransition(getTargetRoot(), args);
831
+ }
832
+
833
+ export async function toolJudgmentLedgerAppend(args) {
834
+ const { judgmentLedgerAppend } = await import('../lib/judgment-writer.js');
835
+ return judgmentLedgerAppend(getTargetRoot(), args);
836
+ }
837
+
838
+ export async function toolJudgmentPersonWrite(args) {
839
+ const { judgmentPersonWrite } = await import('../lib/judgment-writer.js');
840
+ return judgmentPersonWrite(getTargetRoot(), args);
841
+ }
842
+
843
+ export async function toolJudgmentSituationWrite(args) {
844
+ const { judgmentSituationWrite } = await import('../lib/judgment-writer.js');
845
+ return judgmentSituationWrite(getTargetRoot(), args);
846
+ }
847
+
848
+ export async function toolJudgmentGoalWrite(args) {
849
+ const { judgmentGoalWrite } = await import('../lib/judgment-writer.js');
850
+ return judgmentGoalWrite(getTargetRoot(), args);
851
+ }
852
+
853
+ export async function toolGetJudgmentState() {
854
+ const { getJudgmentState } = await import('../lib/judgment-writer.js');
855
+ return getJudgmentState(getTargetRoot());
856
+ }
857
+
809
858
  // ---------------------------------------------------------------------------
810
859
  // COMP-MCP-ENFORCE-1 — phase-scoped MCP tool gate (profile × phase)
811
860
  //
@@ -897,4 +946,3 @@ export function assertToolPhaseAllowed(tool, args = {}, _testCtx) {
897
946
  throw e;
898
947
  }
899
948
  }
900
-
@@ -68,6 +68,15 @@ import {
68
68
  toolGetWorkspace,
69
69
  toolWriteCheckpoint,
70
70
  toolComposeResume,
71
+ toolJudgmentPositionCreate,
72
+ toolJudgmentPositionAmend,
73
+ toolJudgmentJointAdd,
74
+ toolJudgmentTransition,
75
+ toolJudgmentLedgerAppend,
76
+ toolJudgmentPersonWrite,
77
+ toolJudgmentSituationWrite,
78
+ toolJudgmentGoalWrite,
79
+ toolGetJudgmentState,
71
80
  _getBinding,
72
81
  assertToolPhaseAllowed,
73
82
  _getSessionProfile,
@@ -681,6 +690,208 @@ const TOOLS = [
681
690
  },
682
691
  },
683
692
  },
693
+
694
+ // -------------------------------------------------------------------------
695
+ // Judgment writer — COMP-JUDGMENT-WRITER (the only legitimate write path to
696
+ // docs/judgment canon; records canonical, markdown generated). Terse
697
+ // schemas by design (token budget); the writer self-validates against
698
+ // contracts/judgment-record.schema.json — inputSchema is advisory.
699
+ // -------------------------------------------------------------------------
700
+ {
701
+ name: 'judgment_position_create',
702
+ description: 'Create a judgment position revision (new chain, update, supersession via supersedes, or tombstone via retracted). ASSERT grounding requires an elicitation block. Provenance is writer-stamped.',
703
+ inputSchema: {
704
+ type: 'object',
705
+ required: ['slug', 'claims', 'conviction'],
706
+ properties: {
707
+ slug: { type: 'string' },
708
+ claims: { type: 'array', description: 'Claims: {id, text, grounding EXT|INT|ASSERT|DERIVED|AGENT, supports[], elicitation?}.' },
709
+ conviction: { type: 'object', description: '{level high|medium|low, source stated|inferred}' },
710
+ rejected_alternatives: { type: 'array', description: '{what, why}[]' },
711
+ supersedes: { type: 'string', description: '<slug>#r<N> being superseded.' },
712
+ retracted: { type: 'boolean', description: 'true → tombstone revision.' },
713
+ idempotency_key: { type: 'string' },
714
+ },
715
+ },
716
+ },
717
+ {
718
+ name: 'judgment_position_amend',
719
+ description: 'Scoped amendment (P6): new revision changing ONE claim\'s grounding and/or the conviction block. Anything else is supersession via judgment_position_create.',
720
+ inputSchema: {
721
+ type: 'object',
722
+ required: ['slug'],
723
+ properties: {
724
+ slug: { type: 'string' },
725
+ claim_id: { type: 'string' },
726
+ grounding: { type: 'string', description: 'New grounding for claim_id.' },
727
+ elicitation: { type: 'object', description: 'Required if new grounding is ASSERT.' },
728
+ conviction: { type: 'object' },
729
+ idempotency_key: { type: 'string' },
730
+ },
731
+ },
732
+ },
733
+ {
734
+ name: 'judgment_joint_add',
735
+ description: 'Add a judgment joint (born open). Both branches and a coarse cost bucket are required; EXT/STRADDLE method packages may be attached now or at dispose time.',
736
+ inputSchema: {
737
+ type: 'object',
738
+ required: ['slug', 'question', 'branch_true', 'branch_false', 'resolve_by', 'cost', 'rank'],
739
+ properties: {
740
+ slug: { type: 'string' },
741
+ question: { type: 'string' },
742
+ branch_true: { type: 'string' },
743
+ branch_false: { type: 'string' },
744
+ resolve_by: { type: 'string', description: 'EXT|INT|CONSTRUCT|ASSERT|STRADDLE' },
745
+ cost: { type: 'string', description: 'hours|days|weeks|months' },
746
+ rank: { type: 'string', description: 'high|medium' },
747
+ ext: { type: 'object', description: 'EXT package: {sharpened_question, bar, falsifier} or {judgment_dispatch: true, reason}.' },
748
+ straddle: { type: 'object', description: '{discriminating_signal, kill_criteria}' },
749
+ flags: { type: 'array' },
750
+ idempotency_key: { type: 'string' },
751
+ },
752
+ },
753
+ },
754
+ {
755
+ name: 'judgment_transition',
756
+ description: 'Joint state machine (edge→artifact table enforced; Stratum-guarded where capabilities.guard). Provide the target state plus its required artifact (resolution/dissolution/reopen/redispose), and/or a rank change (atomic rank ledger event).',
757
+ inputSchema: {
758
+ type: 'object',
759
+ required: ['slug'],
760
+ properties: {
761
+ slug: { type: 'string' },
762
+ to: { type: 'string', description: 'open|under_test|resolved|inconclusive|superseded|dissolved' },
763
+ resolution: { type: 'object', description: '{outcome resolved|inconclusive|failed_to_run|superseded, …outcome-specific fields, ext_result?, elicitation?}' },
764
+ dissolution: { type: 'object', description: '{decomposed_into[]}' },
765
+ reopen: { type: 'object', description: '{shaken_evidence_ref} (P6)' },
766
+ redispose: { type: 'object', description: '{new_resolve_by, ext?, straddle?} (retry inconclusive with a different method)' },
767
+ ext: { type: 'object', description: 'EXT package attached at dispose time.' },
768
+ straddle: { type: 'object' },
769
+ rank: { type: 'object', description: '{to: high|medium}' },
770
+ idempotency_key: { type: 'string' },
771
+ },
772
+ },
773
+ },
774
+ {
775
+ name: 'judgment_ledger_append',
776
+ description: 'Append a judgment ledger event (kind-specific required fields enforced: commit-decide needs trigger+open_joints+prediction; postmortem needs recall_verdict+attribution and grades prediction_ref; override needs reason). Commit/CONSTRUCT events spawn prediction records.',
777
+ inputSchema: {
778
+ type: 'object',
779
+ required: ['kind', 'title'],
780
+ properties: {
781
+ kind: { type: 'string', description: 'decide|kill|override|escalate|calibrate|postmortem|rank|note|correct|open' },
782
+ title: { type: 'string' },
783
+ body: { type: 'string' },
784
+ refs: { type: 'array' },
785
+ rejected: { type: 'array' },
786
+ conviction: { type: 'object' },
787
+ trigger: { type: 'string', description: 'On decide: earned|forced|exhausted (marks a commit-moment decide).' },
788
+ open_joints: { type: 'array' },
789
+ prediction: { type: 'object', description: '{text, outcome_criteria}' },
790
+ disposition: { type: 'string', description: 'Method disposed with; CONSTRUCT requires prediction.' },
791
+ recall_verdict: { type: 'string' },
792
+ attribution: { type: 'string' },
793
+ prediction_ref: { type: 'string' },
794
+ prediction_grade: { type: 'string', description: 'right|right-wrong-reason|wrong' },
795
+ reason: { type: 'string' },
796
+ rests_on: { type: 'array', description: 'On commit-moment decide: goal clause refs (goal:v<N>#c<N>) the commit rests on; resolved through the effective goal chain.' },
797
+ anchor: { type: 'string', description: 'On note: projection anchor (register-header, joint:<slug>, …).' },
798
+ idempotency_key: { type: 'string' },
799
+ },
800
+ },
801
+ },
802
+ {
803
+ name: 'judgment_person_write',
804
+ description: 'Write person canon by op. create adds a person; open_field supports create, fill, and reopen; edge/load_link support create and remove; correct supports divergence pair/clear. Provenance is writer-stamped.',
805
+ inputSchema: {
806
+ type: 'object',
807
+ required: ['op'],
808
+ properties: {
809
+ op: { type: 'string', enum: ['create', 'add_fact', 'correct', 'open_field', 'edge', 'load_link'] },
810
+ slug: { type: 'string' },
811
+ display_name: { type: 'string' },
812
+ section: { type: 'string', description: 'role|life|stated|revealed' },
813
+ text: { type: 'string' },
814
+ channel: { type: 'string', description: 'said|observed|secondhand|inferred' },
815
+ via: { type: 'string', description: 'Source attribution for a secondhand fact; not provenance.' },
816
+ at: { type: 'string', description: 'ISO date (YYYY-MM-DD).' },
817
+ fact_id: { type: 'string' },
818
+ pair_with: { type: 'string', description: 'Pair stated↔revealed divergence on correct.' },
819
+ clear: { type: 'array', description: 'On correct: exactly ["diverges_with"].', items: { type: 'string' } },
820
+ name: { type: 'string', description: 'open_field create branch.' },
821
+ open_field_id: { type: 'string' },
822
+ filled_by: { type: 'string' },
823
+ reopen: { type: 'boolean' },
824
+ reason: { type: 'string' },
825
+ to: { type: 'string', description: 'edge create branch target person slug.' },
826
+ kind: { type: 'string', description: 'edge create branch kind.' },
827
+ edge_id: { type: 'string' },
828
+ remove: { type: 'boolean' },
829
+ fact: { type: 'string', description: 'load_link create branch fact ID.' },
830
+ carries: { type: 'string' },
831
+ load_link_id: { type: 'string' },
832
+ idempotency_key: { type: 'string' },
833
+ },
834
+ },
835
+ },
836
+ {
837
+ name: 'judgment_situation_write',
838
+ description: 'Write situation-entity canon by op. create adds an entity; owed supports create, give, and reopen; load_link supports create and remove. Provenance is writer-stamped.',
839
+ inputSchema: {
840
+ type: 'object',
841
+ required: ['op'],
842
+ properties: {
843
+ op: { type: 'string', enum: ['create', 'add_fact', 'correct', 'owed', 'load_link'] },
844
+ slug: { type: 'string' },
845
+ display_name: { type: 'string' },
846
+ text: { type: 'string' },
847
+ channel: { type: 'string', description: 'said|observed|secondhand|inferred' },
848
+ via: { type: 'string', description: 'Source attribution for a secondhand fact; not provenance.' },
849
+ at: { type: 'string', description: 'ISO date (YYYY-MM-DD).' },
850
+ fact_id: { type: 'string' },
851
+ name: { type: 'string', description: 'owed create branch.' },
852
+ why_load_bearing: { type: 'string' },
853
+ owed_id: { type: 'string' },
854
+ filled_by: { type: 'string' },
855
+ reopen: { type: 'boolean' },
856
+ reason: { type: 'string' },
857
+ remove: { type: 'boolean' },
858
+ fact: { type: 'string', description: 'load_link create branch fact ID.' },
859
+ carries: { type: 'string' },
860
+ load_link_id: { type: 'string' },
861
+ idempotency_key: { type: 'string' },
862
+ },
863
+ },
864
+ },
865
+ {
866
+ name: 'judgment_goal_write',
867
+ description: 'Write goal canon by op. Ordinary cuts require elicited clauses, provocation, and owner ratification. Every non-migrate op is migration-locked while a live legacy objective requires migration; migrate is the one-shot, fail-closed cutover that lifts the lock and takes no payload. joint_link/load_link support create and remove. Provenance and intents are writer-owned.',
868
+ inputSchema: {
869
+ type: 'object',
870
+ required: ['op'],
871
+ properties: {
872
+ op: { type: 'string', enum: ['cut', 'correct', 'joint_link', 'load_link', 'migrate'] },
873
+ clauses: { type: 'array', description: 'cut: {text, channel, elicitation, secondhand source attribution?}[]' },
874
+ provocation: { type: 'object', description: 'cut: {quote, at}' },
875
+ ratification: { type: 'object', description: 'cut: {asked, answered_at, answer_ref, quote}' },
876
+ diff_note: { type: 'string' },
877
+ clause_id: { type: 'string', description: 'correct branch clause ID.' },
878
+ text: { type: 'string', description: 'correct branch replacement wording.' },
879
+ joint: { type: 'string', description: 'joint_link create branch joint slug.' },
880
+ joint_link_id: { type: 'string' },
881
+ remove: { type: 'boolean' },
882
+ reason: { type: 'string' },
883
+ clause: { type: 'string', description: 'load_link create branch v<N>#c<N> reference.' },
884
+ carries: { type: 'string' },
885
+ load_link_id: { type: 'string' },
886
+ idempotency_key: { type: 'string' },
887
+ },
888
+ },
889
+ },
890
+ {
891
+ name: 'get_judgment_state',
892
+ description: 'Judgment canon snapshot: positions (derived status), joints, under-test, open predictions, recent ledger. Replays pending judgment intents first.',
893
+ inputSchema: { type: 'object', properties: {} },
894
+ },
684
895
  ];
685
896
 
686
897
  // ---------------------------------------------------------------------------
@@ -774,6 +985,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
774
985
  case 'propose_followup': result = await toolProposeFollowup(args); break;
775
986
  case 'write_checkpoint': result = await toolWriteCheckpoint(args); break;
776
987
  case 'compose_resume': result = await toolComposeResume(args); break;
988
+ case 'judgment_position_create': result = await toolJudgmentPositionCreate(args); break;
989
+ case 'judgment_position_amend': result = await toolJudgmentPositionAmend(args); break;
990
+ case 'judgment_joint_add': result = await toolJudgmentJointAdd(args); break;
991
+ case 'judgment_transition': result = await toolJudgmentTransition(args); break;
992
+ case 'judgment_ledger_append': result = await toolJudgmentLedgerAppend(args); break;
993
+ case 'judgment_person_write': result = await toolJudgmentPersonWrite(args); break;
994
+ case 'judgment_situation_write': result = await toolJudgmentSituationWrite(args); break;
995
+ case 'judgment_goal_write': result = await toolJudgmentGoalWrite(args); break;
996
+ case 'get_judgment_state': result = await toolGetJudgmentState(args); break;
777
997
  // agent_run removed — STRAT-DEDUP-AGENTRUN v1. Use mcp__stratum__stratum_agent_run.
778
998
  default:
779
999
  return {
@@ -36,6 +36,7 @@ const REVIEWER_ALLOW = [
36
36
  'get_pending_gates', 'get_changelog_entries', 'get_journal_entries', 'get_completions',
37
37
  'validate_feature', 'validate_project', 'roadmap_diff', 'get_roadmap', 'roadmap_graph_check', 'assess_feature_artifacts',
38
38
  'set_workspace', 'get_workspace', 'bind_session',
39
+ 'get_judgment_state', // COMP-JUDGMENT-WRITER: read-only; the eight judgment write tools stay reviewer-denied
39
40
  ];
40
41
 
41
42
  export const PROFILE_POLICY = {