@smartmemory/compose 0.2.6-beta → 0.2.8-beta

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 (70) hide show
  1. package/bin/compose.js +45 -3
  2. package/bin/git-hooks/pre-push.template +41 -13
  3. package/contracts/gsd-stuck.json +141 -0
  4. package/dist/assets/{App-j8fWZcGr.js → App-D3ehVPvi.js} +4 -4
  5. package/dist/assets/{arc-BFqOo_jJ.js → arc-Dmf69iHG.js} +1 -1
  6. package/dist/assets/{architectureDiagram-3BPJPVTR-D722w0RE.js → architectureDiagram-3BPJPVTR-xYo993Yw.js} +1 -1
  7. package/dist/assets/{blockDiagram-GPEHLZMM-B4w0mOAJ.js → blockDiagram-GPEHLZMM-UX4EF98O.js} +1 -1
  8. package/dist/assets/{c4Diagram-AAUBKEIU-D6LE8-j8.js → c4Diagram-AAUBKEIU-DaP9CGWb.js} +1 -1
  9. package/dist/assets/channel-D_RXsFFT.js +1 -0
  10. package/dist/assets/{chunk-2J33WTMH-CrazA7xu.js → chunk-2J33WTMH-CKk_RN3A.js} +1 -1
  11. package/dist/assets/{chunk-4BX2VUAB-Cp90GiCM.js → chunk-4BX2VUAB-DboAwYKw.js} +1 -1
  12. package/dist/assets/{chunk-55IACEB6-Bnais1SK.js → chunk-55IACEB6-Dsy9RYvI.js} +1 -1
  13. package/dist/assets/{chunk-727SXJPM-kD07Sqp5.js → chunk-727SXJPM-fAH0QO9v.js} +1 -1
  14. package/dist/assets/{chunk-AQP2D5EJ-DmIxhJc8.js → chunk-AQP2D5EJ-DyZYerFP.js} +1 -1
  15. package/dist/assets/{chunk-FMBD7UC4-Jti_und8.js → chunk-FMBD7UC4-BnboGO5t.js} +1 -1
  16. package/dist/assets/{chunk-ND2GUHAM-Ipx3noKz.js → chunk-ND2GUHAM-Di9tYXme.js} +1 -1
  17. package/dist/assets/{chunk-QZHKN3VN-CeblRnPF.js → chunk-QZHKN3VN-zRPRlAIL.js} +1 -1
  18. package/dist/assets/classDiagram-4FO5ZUOK-K6wdB4ic.js +1 -0
  19. package/dist/assets/classDiagram-v2-Q7XG4LA2-K6wdB4ic.js +1 -0
  20. package/dist/assets/{cose-bilkent-S5V4N54A-fNQlSmHt.js → cose-bilkent-S5V4N54A-C7Hqukaf.js} +1 -1
  21. package/dist/assets/{dagre-BM42HDAG-D27D6YAL.js → dagre-BM42HDAG-B-cR-BjI.js} +1 -1
  22. package/dist/assets/{diagram-2AECGRRQ-CtXeohzN.js → diagram-2AECGRRQ-B6-5onDk.js} +1 -1
  23. package/dist/assets/{diagram-5GNKFQAL-C_BqZkx0.js → diagram-5GNKFQAL-DoZZgFAM.js} +1 -1
  24. package/dist/assets/{diagram-KO2AKTUF-B29ynQz4.js → diagram-KO2AKTUF-77jEGlJh.js} +1 -1
  25. package/dist/assets/{diagram-LMA3HP47-DAYJMc2I.js → diagram-LMA3HP47-D3S7XDRD.js} +1 -1
  26. package/dist/assets/{diagram-OG6HWLK6-CBJMis3l.js → diagram-OG6HWLK6-KbYL9aCY.js} +1 -1
  27. package/dist/assets/{erDiagram-TEJ5UH35-nd3GWiPn.js → erDiagram-TEJ5UH35-DezFbJP-.js} +1 -1
  28. package/dist/assets/{flowDiagram-I6XJVG4X-HFUno_nV.js → flowDiagram-I6XJVG4X-4x31cK9j.js} +1 -1
  29. package/dist/assets/{ganttDiagram-6RSMTGT7-CPPAAjwR.js → ganttDiagram-6RSMTGT7-FopfSTyZ.js} +1 -1
  30. package/dist/assets/{gitGraphDiagram-PVQCEYII-NBq1F6K2.js → gitGraphDiagram-PVQCEYII-DSiQGKbN.js} +1 -1
  31. package/dist/assets/graph-Cs_vqCR0.js +331 -0
  32. package/dist/assets/{index-uHKnp74B.js → index-ClX6LVAf.js} +2 -2
  33. package/dist/assets/{infoDiagram-5YYISTIA-D-TOBtCq.js → infoDiagram-5YYISTIA-DE6BqzK_.js} +1 -1
  34. package/dist/assets/{ishikawaDiagram-YF4QCWOH-nXOztZiZ.js → ishikawaDiagram-YF4QCWOH-Dml8NwQI.js} +1 -1
  35. package/dist/assets/{journeyDiagram-JHISSGLW-Bko3tTdh.js → journeyDiagram-JHISSGLW-CwWeJgjE.js} +1 -1
  36. package/dist/assets/{kanban-definition-UN3LZRKU-1e-7i8st.js → kanban-definition-UN3LZRKU-DnG956Wh.js} +1 -1
  37. package/dist/assets/{linear-Dx5ZJB7F.js → linear-CA3N7Rpi.js} +1 -1
  38. package/dist/assets/{mindmap-definition-RKZ34NQL-CNwNkDqN.js → mindmap-definition-RKZ34NQL-CxfIOjLX.js} +1 -1
  39. package/dist/assets/{pieDiagram-4H26LBE5-C5fvCej-.js → pieDiagram-4H26LBE5-O7aIwy1x.js} +1 -1
  40. package/dist/assets/{quadrantDiagram-W4KKPZXB-4NoQsF61.js → quadrantDiagram-W4KKPZXB-CPQ2qq7c.js} +1 -1
  41. package/dist/assets/{requirementDiagram-4Y6WPE33-q5WxB9LO.js → requirementDiagram-4Y6WPE33-C23horL4.js} +1 -1
  42. package/dist/assets/{sankeyDiagram-5OEKKPKP-DlQNB367.js → sankeyDiagram-5OEKKPKP-DPY04kOW.js} +1 -1
  43. package/dist/assets/{sequenceDiagram-3UESZ5HK-BzHclOKt.js → sequenceDiagram-3UESZ5HK-BKaTfIvo.js} +1 -1
  44. package/dist/assets/{stateDiagram-AJRCARHV-BvWRI9zK.js → stateDiagram-AJRCARHV-B9na_6mY.js} +1 -1
  45. package/dist/assets/stateDiagram-v2-BHNVJYJU-Cf84VDiH.js +1 -0
  46. package/dist/assets/{timeline-definition-PNZ67QCA-j2wKjAti.js → timeline-definition-PNZ67QCA-BBWPqd7X.js} +1 -1
  47. package/dist/assets/{vennDiagram-CIIHVFJN-B77g7htC.js → vennDiagram-CIIHVFJN-tWqiHsOZ.js} +1 -1
  48. package/dist/assets/{wardley-L42UT6IY-83Im2mo2.js → wardley-L42UT6IY-DorxG6os.js} +1 -1
  49. package/dist/assets/{wardleyDiagram-YWT4CUSO-CK-XB-bO.js → wardleyDiagram-YWT4CUSO-B49f8GzW.js} +1 -1
  50. package/dist/assets/{xychartDiagram-2RQKCTM6-D42FcVOY.js → xychartDiagram-2RQKCTM6-BgKSj8Qb.js} +1 -1
  51. package/dist/index.html +1 -1
  52. package/lib/budget-ledger.js +84 -0
  53. package/lib/build-stream-schema.js +5 -3
  54. package/lib/build.js +91 -2
  55. package/lib/feature-validator.js +40 -8
  56. package/lib/gsd-budget.js +205 -0
  57. package/lib/gsd-stuck.js +275 -0
  58. package/lib/gsd.js +499 -8
  59. package/lib/result-normalizer.js +5 -1
  60. package/package.json +2 -2
  61. package/server/agent-spawn.js +7 -1
  62. package/server/compose-mcp-tools.js +103 -1
  63. package/server/compose-mcp.js +34 -4
  64. package/server/design-routes.js +4 -1
  65. package/server/mcp-tool-policy.js +112 -0
  66. package/dist/assets/channel-BD-5_hPW.js +0 -1
  67. package/dist/assets/classDiagram-4FO5ZUOK-mSW5R7DY.js +0 -1
  68. package/dist/assets/classDiagram-v2-Q7XG4LA2-mSW5R7DY.js +0 -1
  69. package/dist/assets/graph-CJVNlri5.js +0 -331
  70. package/dist/assets/stateDiagram-v2-BHNVJYJU-CDlF0VA8.js +0 -1
package/bin/compose.js CHANGED
@@ -1654,6 +1654,13 @@ Exit codes:
1654
1654
  else if (a === '--code') code = args[++i]
1655
1655
  else if (a.startsWith('--block-on=')) blockOn = a.slice('--block-on='.length)
1656
1656
  else if (a === '--block-on') blockOn = args[++i]
1657
+ else if (a === '--workspace' || a.startsWith('--workspace=')) {
1658
+ // --workspace is a valid global flag consumed by resolveCwdWithWorkspace(args)
1659
+ // below; skip it here so the unknown-flag guard doesn't reject it. Don't
1660
+ // consume the next token: a bare `--workspace <id>` leaves <id> as a
1661
+ // positional (harmlessly ignored by this loop, parsed by resolveCwdWith-
1662
+ // Workspace), so `--workspace --help` still resolves --help correctly.
1663
+ }
1657
1664
  else if (a.startsWith('--')) {
1658
1665
  console.error(`Unknown flag: ${a}`)
1659
1666
  process.exit(2)
@@ -1962,14 +1969,22 @@ if (cmd === 'build') {
1962
1969
  // pipeline (pipelines/gsd.stratum.yaml). Hard-requires existing
1963
1970
  // docs/features/<code>/blueprint.md with a parseable Boundary Map.
1964
1971
  const gsdCode = args.find(a => !a.startsWith('-'))
1972
+ const gsdResume = args.includes('--resume')
1973
+ const gsdResetBudget = args.includes('--reset-budget')
1965
1974
  if (!gsdCode) {
1966
- console.error('Usage: compose gsd <feature-code>')
1975
+ console.error('Usage: compose gsd <feature-code> [--resume] [--reset-budget]')
1967
1976
  console.error('')
1968
1977
  console.error('Runs the per-task fresh-context dispatch pipeline (COMP-GSD-2).')
1969
1978
  console.error('Hard-requires docs/features/<code>/blueprint.md with a valid Boundary Map.')
1979
+ console.error('Detects stuck tasks (COMP-GSD-5) and halts with a structured diagnostic.')
1980
+ console.error('Enforces budget ceilings (COMP-GSD-4) from .compose/compose.json gsd.budget.*')
1970
1981
  console.error('')
1971
1982
  console.error('Options:')
1972
- console.error(' --cwd <path> Working directory (defaults to current)')
1983
+ console.error(' --resume Resume a halted run: re-dispatch the unfinished tasks')
1984
+ console.error(' from .compose/gsd/<code>/pause.json (skips completed tasks).')
1985
+ console.error(' --reset-budget Clear the feature\'s cumulative budget ledger before running')
1986
+ console.error(' (use after raising or removing a spent gsd.budget.cumulative cap).')
1987
+ console.error(' --cwd <path> Working directory (defaults to current)')
1973
1988
  process.exit(1)
1974
1989
  }
1975
1990
  const { root: gsdCwd } = resolveCwdWithWorkspace(args)
@@ -1977,7 +1992,34 @@ if (cmd === 'build') {
1977
1992
  const gsdAgentCwd = cwdIdx !== -1 ? resolve(args[cwdIdx + 1]) : gsdCwd
1978
1993
  const { runGsd } = await import('../lib/gsd.js')
1979
1994
  try {
1980
- const result = await runGsd(gsdCode, { cwd: gsdAgentCwd })
1995
+ if (gsdResetBudget) {
1996
+ // COMP-GSD-4: clear the cumulative ledger so a spent ceiling no longer
1997
+ // refuses the run. Runs before dispatch; per-run windows reset anyway.
1998
+ const { resetGsdUsage } = await import('../lib/budget-ledger.js')
1999
+ resetGsdUsage(resolve(gsdAgentCwd, '.compose'), gsdCode)
2000
+ console.log(`gsd: cleared cumulative budget ledger for ${gsdCode}.`)
2001
+ }
2002
+ const result = await runGsd(gsdCode, { cwd: gsdAgentCwd, resume: gsdResume })
2003
+ if (result.status === 'stuck') {
2004
+ // COMP-GSD-5: a stuck halt is a clean, recoverable stop — not a crash.
2005
+ console.error(`gsd stuck: task ${result.stuckTaskId} tripped the ${result.signal} detector.`)
2006
+ console.error(`Diagnostic: .compose/gsd/${gsdCode}/stuck.md`)
2007
+ console.error(`Resume with: compose gsd ${gsdCode} --resume`)
2008
+ process.exit(2)
2009
+ }
2010
+ if (result.status === 'budget') {
2011
+ // COMP-GSD-4: a budget halt is a clean, recoverable stop — not a crash.
2012
+ if (result.axis === 'cumulative') {
2013
+ console.error(`gsd budget: cumulative ceiling for ${gsdCode} is already spent.`)
2014
+ console.error(`Diagnostic: .compose/gsd/${gsdCode}/budget.md`)
2015
+ console.error(`Raise gsd.budget.cumulative.* or clear it: compose gsd ${gsdCode} --reset-budget`)
2016
+ } else {
2017
+ console.error(`gsd budget: the ${result.axis} ceiling tripped mid-run.`)
2018
+ console.error(`Diagnostic: .compose/gsd/${gsdCode}/budget.md`)
2019
+ console.error(`Raise gsd.budget.* and resume: compose gsd ${gsdCode} --resume`)
2020
+ }
2021
+ process.exit(2)
2022
+ }
1981
2023
  console.log(`gsd complete: ${result.blackboardEntries} task results captured.`)
1982
2024
  } catch (err) {
1983
2025
  console.error(`gsd failed: ${err.message}`)
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env bash
2
- # Compose pre-push hook — runs `compose validate` and blocks the push on
3
- # any error-severity drift finding. Installed by `compose hooks install --pre-push`;
4
- # placeholders below are substituted at install time.
2
+ # Compose pre-push hook — blocks the push on a red `npm test` suite (HARD gate) and
3
+ # runs `compose validate` as an ADVISORY drift check (prints findings, does not
4
+ # block re-enable strict drift-blocking once the repo's drift backlog is
5
+ # reconciled). Installed by `compose hooks install --pre-push`; placeholders
6
+ # below are substituted at install time.
5
7
  #
6
8
  # External-reference (xref) resolution is OFF here by default: github xref:
7
9
  # citations / kind:"external" links emit XREF_RESOLUTION_SKIPPED (warning,
@@ -18,18 +20,44 @@ COMPOSE_WORKSPACE_ID="__COMPOSE_WORKSPACE_ID__"
18
20
  LOG="${COMPOSE_HOOK_LOG:-.compose/data/pre-push.log}"
19
21
  mkdir -p "$(dirname "$LOG")" 2>/dev/null || true
20
22
 
23
+ # Drift check (ADVISORY — COMP-RESUME follow-up): print findings for visibility,
24
+ # never block. The repo carries pre-existing error-severity drift (much from the
25
+ # COMP-MCP-ENFORCE lifecycle-as-truth projection); hard-blocking on it would make
26
+ # the whole hook unusable, so the TEST gate below is the hard blocker. Re-enable
27
+ # strict drift-blocking once the backlog is reconciled (tracked separately).
21
28
  OUTPUT=$("$COMPOSE_NODE" "$COMPOSE_BIN" validate --scope=project --block-on=error --workspace="$COMPOSE_WORKSPACE_ID" 2>&1)
22
- EXIT_CODE=$?
23
-
24
- if [ "$EXIT_CODE" -ne 0 ]; then
25
- echo "$OUTPUT" | tee -a "$LOG" >&2
29
+ VALIDATE_EXIT=$?
30
+ echo "$OUTPUT"
31
+ if [ "$VALIDATE_EXIT" -ne 0 ]; then
26
32
  echo "" >&2
27
- echo "compose validate found error-severity drift. Push aborted." >&2
28
- echo "Run \`compose validate\` to see findings, then fix and retry." >&2
29
- echo "Bypass at your own risk: git push --no-verify" >&2
30
- exit "$EXIT_CODE"
33
+ echo "pre-push: compose validate reported drift (ADVISORY — not blocking). Review with \`compose validate\`." >&2
34
+ fi
35
+
36
+ # ── Test gate (COMP-RESUME follow-up) ───────────────────────────────────────
37
+ # Block the push if the suite is red. This is the gate whose absence let a broken
38
+ # integration test (and a stale-schema consumer bug) reach main — nothing ran the
39
+ # suite before push. Heavy (full `npm test`) but authoritative. Auto-skips in a
40
+ # repo with no npm `test` script. Emergency bypass (sparingly): git push --no-verify
41
+ if [ -f package.json ]; then
42
+ # Probe scripts.test: exit 0 = present, 1 = definitively absent, anything else
43
+ # = could not read package.json. Skip ONLY on a definitive "absent" (1); on
44
+ # ambiguity run the suite anyway so a broken/unreadable package.json fails
45
+ # CLOSED (blocks), never open (silently skips the gate).
46
+ "$COMPOSE_NODE" -e "let s={};try{s=require(process.cwd()+'/package.json').scripts||{}}catch(e){process.exit(3)};process.exit(s.test?0:1)" 2>/dev/null
47
+ PROBE=$?
48
+ if [ "$PROBE" -ne 1 ]; then
49
+ echo "pre-push: running full test suite (npm test)…" >&2
50
+ PATH="$(dirname "$COMPOSE_NODE"):$PATH" npm test >> "$LOG" 2>&1
51
+ TEST_EXIT=$?
52
+ if [ "$TEST_EXIT" -ne 0 ]; then
53
+ tail -n 40 "$LOG" >&2
54
+ echo "" >&2
55
+ echo "npm test failed — push aborted (full log: $LOG)." >&2
56
+ echo "Bypass at your own risk: git push --no-verify" >&2
57
+ exit "$TEST_EXIT"
58
+ fi
59
+ echo "pre-push: test suite green." >&2
60
+ fi
31
61
  fi
32
62
 
33
- # Below-threshold findings (warnings/info) are still printed for visibility.
34
- echo "$OUTPUT"
35
63
  exit 0
@@ -0,0 +1,141 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gsd-stuck.json",
4
+ "_source": "COMP-GSD-5",
5
+ "_also": "COMP-GSD-4",
6
+ "_roadmap": "COMP-GSD-5",
7
+ "title": "GsdStuckContracts",
8
+ "description": "Artifacts written when a `compose gsd` run halts. A STUCK halt (COMP-GSD-5) writes stuck.json/stuck.md; a BUDGET halt (COMP-GSD-4) writes budget.json/budget.md. Both persist `pause.json` (machine-readable resume state); `compose gsd <feature> --resume` reads it, validates ownership/mode, and re-dispatches decomposedTasks minus completedTaskIds (completed results already live in the blackboard). The pause `kind` field (optional; absent ⇒ 'stuck' for back-compat) discriminates: a 'stuck' pause carries the stuck-specific fields, a 'budget' pause carries the `budget` block. GSD-6 reuses the `pause` shape for automatic crash-recovery.",
9
+ "definitions": {
10
+ "stuck": {
11
+ "type": "object",
12
+ "title": "GsdStuckDiagnostic",
13
+ "description": "Structured diagnostic emitted on the first stuck verdict for a task.",
14
+ "required": ["feature", "taskId", "signal", "detail", "attemptCounts", "ts"],
15
+ "additionalProperties": false,
16
+ "properties": {
17
+ "feature": {
18
+ "type": "string",
19
+ "description": "Feature code the gsd run is building (e.g. COMP-GSD-5)."
20
+ },
21
+ "taskId": {
22
+ "type": "string",
23
+ "description": "The decomposed task id that tripped the detector."
24
+ },
25
+ "signal": {
26
+ "type": "string",
27
+ "enum": ["same_file", "error_recurrence", "no_progress", "wall_clock"],
28
+ "description": "Which stuck pattern fired. same_file: one file edited >= sameFileEdits times. error_recurrence: a normalized error hash recurred >= errorRepeats. no_progress: >= noProgressCalls consecutive non-file-changing tool calls. wall_clock: task ran >= wallClockMs without finishing."
29
+ },
30
+ "detail": {
31
+ "type": "string",
32
+ "description": "Human-readable explanation of the offending file / error / stall."
33
+ },
34
+ "attemptCounts": {
35
+ "type": "object",
36
+ "description": "Snapshot of the per-task counters at the moment of the verdict.",
37
+ "properties": {
38
+ "sameFileEdits": { "type": "integer", "minimum": 0, "description": "Max edit count across files for this task." },
39
+ "errorRepeats": { "type": "integer", "minimum": 0, "description": "Max repeat count across normalized error hashes." },
40
+ "noProgressCalls": { "type": "integer", "minimum": 0, "description": "Current consecutive non-file-changing tool-call count." }
41
+ },
42
+ "additionalProperties": true
43
+ },
44
+ "partialDiff": {
45
+ "type": "string",
46
+ "description": "Optional unified diff of the stuck task's worktree at halt time, for triage."
47
+ },
48
+ "ts": {
49
+ "type": "string",
50
+ "format": "date-time",
51
+ "description": "ISO-8601 timestamp of the verdict."
52
+ }
53
+ }
54
+ },
55
+ "pause": {
56
+ "type": "object",
57
+ "title": "GsdPauseState",
58
+ "description": "Resume state persisted on a stuck OR budget halt. Drives blackboard-driven re-dispatch on --resume — NOT mid-task re-entry. Base required fields are kind-agnostic; the `kind` field (optional; absent ⇒ stuck) selects which extra fields are required via the if/then/else at the end.",
59
+ "required": ["flowId", "stepId", "decomposedTasks", "completedTaskIds", "pid", "mode", "ts"],
60
+ "additionalProperties": false,
61
+ "properties": {
62
+ "kind": {
63
+ "type": "string",
64
+ "enum": ["stuck", "budget"],
65
+ "description": "Halt kind. Optional for back-compat: an absent kind is interpreted as 'stuck'. Determines which extra fields are required (see if/then/else)."
66
+ },
67
+ "budget": {
68
+ "type": "object",
69
+ "description": "Present on a budget halt (kind='budget'). The enforced axis that tripped + the stratum budget_state snapshot.",
70
+ "required": ["axis", "caps", "consumed"],
71
+ "additionalProperties": true,
72
+ "properties": {
73
+ "axis": {
74
+ "type": ["string", "null"],
75
+ "enum": ["ms", "max_agent_dispatches", "max_tokens", "usd", null],
76
+ "description": "Which enforced stratum axis reached its cap (null if indeterminate)."
77
+ },
78
+ "caps": { "type": "object", "description": "The flow budget caps {ms?,max_agent_dispatches?,max_tokens?,usd?}." },
79
+ "consumed": { "type": "object", "description": "Consumed totals {tokens,dispatches,wall_s,dollars}." }
80
+ }
81
+ },
82
+ "flowId": {
83
+ "type": "string",
84
+ "description": "Stratum flow id of the halted run (informational; resume opens a fresh flow)."
85
+ },
86
+ "stepId": {
87
+ "type": "string",
88
+ "description": "The parallel_dispatch step id that was cancelled (e.g. execute)."
89
+ },
90
+ "stuckTaskId": {
91
+ "type": "string",
92
+ "description": "The task id that tripped the detector."
93
+ },
94
+ "signal": {
95
+ "type": "string",
96
+ "enum": ["same_file", "error_recurrence", "no_progress", "wall_clock"],
97
+ "description": "Which stuck pattern fired (mirrors stuck.signal)."
98
+ },
99
+ "detail": {
100
+ "type": "string",
101
+ "description": "Human-readable explanation (mirrors stuck.detail)."
102
+ },
103
+ "decomposedTasks": {
104
+ "type": "array",
105
+ "minItems": 1,
106
+ "description": "The full decomposed task list, persisted so --resume does NOT re-decompose (stable task ids).",
107
+ "items": { "type": "object" }
108
+ },
109
+ "completedTaskIds": {
110
+ "type": "array",
111
+ "description": "Task ids whose VALIDATED result is already in the blackboard. --resume skips these.",
112
+ "items": { "type": "string" }
113
+ },
114
+ "pid": {
115
+ "type": "integer",
116
+ "description": "OS pid of the process that wrote the pause file. --resume refuses if this pid is still alive (another owner)."
117
+ },
118
+ "mode": {
119
+ "type": "string",
120
+ "const": "gsd",
121
+ "description": "Run mode. --resume refuses if this is not 'gsd' (mirrors `compose fix --resume` mode guard)."
122
+ },
123
+ "ts": {
124
+ "type": "string",
125
+ "format": "date-time",
126
+ "description": "ISO-8601 timestamp the pause file was written."
127
+ }
128
+ },
129
+ "if": {
130
+ "properties": { "kind": { "const": "budget" } },
131
+ "required": ["kind"]
132
+ },
133
+ "then": {
134
+ "required": ["budget"]
135
+ },
136
+ "else": {
137
+ "required": ["stuckTaskId", "signal", "detail"]
138
+ }
139
+ }
140
+ }
141
+ }