@smartmemory/compose 0.2.7-beta → 0.2.9-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.
- package/bin/compose.js +112 -3
- package/contracts/gsd-state.json +140 -0
- package/contracts/gsd-stuck.json +141 -0
- package/dist/assets/{App-D3ehVPvi.js → App-CG-2euMe.js} +164 -164
- package/dist/assets/{arc-Dmf69iHG.js → arc-7QBWoLra.js} +1 -1
- package/dist/assets/{architectureDiagram-3BPJPVTR-xYo993Yw.js → architectureDiagram-3BPJPVTR-CUw-7uLm.js} +1 -1
- package/dist/assets/{blockDiagram-GPEHLZMM-UX4EF98O.js → blockDiagram-GPEHLZMM-COU1vmr7.js} +1 -1
- package/dist/assets/{c4Diagram-AAUBKEIU-DaP9CGWb.js → c4Diagram-AAUBKEIU-XPO9PSJL.js} +1 -1
- package/dist/assets/channel-Bcu04MIK.js +1 -0
- package/dist/assets/{chunk-2J33WTMH-CKk_RN3A.js → chunk-2J33WTMH-zMzVB2a6.js} +1 -1
- package/dist/assets/{chunk-4BX2VUAB-DboAwYKw.js → chunk-4BX2VUAB-Kke_qcHU.js} +1 -1
- package/dist/assets/{chunk-55IACEB6-Dsy9RYvI.js → chunk-55IACEB6-hMeFx5Nh.js} +1 -1
- package/dist/assets/{chunk-727SXJPM-fAH0QO9v.js → chunk-727SXJPM-DesUnrEw.js} +1 -1
- package/dist/assets/{chunk-AQP2D5EJ-DyZYerFP.js → chunk-AQP2D5EJ-1uGGvkxW.js} +1 -1
- package/dist/assets/{chunk-FMBD7UC4-BnboGO5t.js → chunk-FMBD7UC4-DYHv1PcZ.js} +1 -1
- package/dist/assets/{chunk-ND2GUHAM-Di9tYXme.js → chunk-ND2GUHAM-D0MENOLX.js} +1 -1
- package/dist/assets/{chunk-QZHKN3VN-zRPRlAIL.js → chunk-QZHKN3VN-8nn3HP-N.js} +1 -1
- package/dist/assets/classDiagram-4FO5ZUOK-DU4yxldU.js +1 -0
- package/dist/assets/classDiagram-v2-Q7XG4LA2-DU4yxldU.js +1 -0
- package/dist/assets/{cose-bilkent-S5V4N54A-C7Hqukaf.js → cose-bilkent-S5V4N54A-BoZPVIny.js} +1 -1
- package/dist/assets/{dagre-BM42HDAG-B-cR-BjI.js → dagre-BM42HDAG-BgZzdLG9.js} +1 -1
- package/dist/assets/{diagram-2AECGRRQ-B6-5onDk.js → diagram-2AECGRRQ-CknAnpSu.js} +1 -1
- package/dist/assets/{diagram-5GNKFQAL-DoZZgFAM.js → diagram-5GNKFQAL-CZUEbKim.js} +1 -1
- package/dist/assets/{diagram-KO2AKTUF-77jEGlJh.js → diagram-KO2AKTUF-DCs-pLdH.js} +1 -1
- package/dist/assets/{diagram-LMA3HP47-D3S7XDRD.js → diagram-LMA3HP47-lRaDjIfM.js} +1 -1
- package/dist/assets/{diagram-OG6HWLK6-KbYL9aCY.js → diagram-OG6HWLK6-CIGqmehP.js} +1 -1
- package/dist/assets/{erDiagram-TEJ5UH35-DezFbJP-.js → erDiagram-TEJ5UH35-Lx3c2N6F.js} +1 -1
- package/dist/assets/{flowDiagram-I6XJVG4X-4x31cK9j.js → flowDiagram-I6XJVG4X-VoluKqSq.js} +1 -1
- package/dist/assets/{ganttDiagram-6RSMTGT7-FopfSTyZ.js → ganttDiagram-6RSMTGT7-D7hETiNZ.js} +1 -1
- package/dist/assets/{gitGraphDiagram-PVQCEYII-DSiQGKbN.js → gitGraphDiagram-PVQCEYII-DenEcUvY.js} +1 -1
- package/dist/assets/{index-ClX6LVAf.js → index-B4dv3acY.js} +2 -2
- package/dist/assets/{infoDiagram-5YYISTIA-DE6BqzK_.js → infoDiagram-5YYISTIA-v7cq9Er9.js} +1 -1
- package/dist/assets/{ishikawaDiagram-YF4QCWOH-Dml8NwQI.js → ishikawaDiagram-YF4QCWOH-CfCCXt2x.js} +1 -1
- package/dist/assets/{journeyDiagram-JHISSGLW-CwWeJgjE.js → journeyDiagram-JHISSGLW-Bbokl_xO.js} +1 -1
- package/dist/assets/{kanban-definition-UN3LZRKU-DnG956Wh.js → kanban-definition-UN3LZRKU-DhkOZ2hg.js} +1 -1
- package/dist/assets/{linear-CA3N7Rpi.js → linear-bHjluRm2.js} +1 -1
- package/dist/assets/{mindmap-definition-RKZ34NQL-CxfIOjLX.js → mindmap-definition-RKZ34NQL-C1bHpoXH.js} +1 -1
- package/dist/assets/{pieDiagram-4H26LBE5-O7aIwy1x.js → pieDiagram-4H26LBE5-CZb1i55T.js} +1 -1
- package/dist/assets/{quadrantDiagram-W4KKPZXB-CPQ2qq7c.js → quadrantDiagram-W4KKPZXB-o37AwRHB.js} +1 -1
- package/dist/assets/{requirementDiagram-4Y6WPE33-C23horL4.js → requirementDiagram-4Y6WPE33-BVErWDzU.js} +1 -1
- package/dist/assets/{sankeyDiagram-5OEKKPKP-DPY04kOW.js → sankeyDiagram-5OEKKPKP-BhBK8gHQ.js} +1 -1
- package/dist/assets/{sequenceDiagram-3UESZ5HK-BKaTfIvo.js → sequenceDiagram-3UESZ5HK-CsICF23P.js} +1 -1
- package/dist/assets/{stateDiagram-AJRCARHV-B9na_6mY.js → stateDiagram-AJRCARHV-TN1AXwim.js} +1 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-BLR6AkKX.js +1 -0
- package/dist/assets/{timeline-definition-PNZ67QCA-BBWPqd7X.js → timeline-definition-PNZ67QCA-DftAajbU.js} +1 -1
- package/dist/assets/{vennDiagram-CIIHVFJN-tWqiHsOZ.js → vennDiagram-CIIHVFJN-cFTMstT7.js} +1 -1
- package/dist/assets/{wardley-L42UT6IY-DorxG6os.js → wardley-L42UT6IY-DL8CivzO.js} +1 -1
- package/dist/assets/{wardleyDiagram-YWT4CUSO-B49f8GzW.js → wardleyDiagram-YWT4CUSO-BDZT1hQj.js} +1 -1
- package/dist/assets/{xychartDiagram-2RQKCTM6-BgKSj8Qb.js → xychartDiagram-2RQKCTM6-DQQSkfC4.js} +1 -1
- package/dist/index.html +1 -1
- package/lib/budget-ledger.js +84 -0
- package/lib/build-stream-schema.js +5 -3
- package/lib/build.js +122 -2
- package/lib/feature-validator.js +40 -8
- package/lib/gsd-budget.js +205 -0
- package/lib/gsd-diff-capture.js +34 -0
- package/lib/gsd-events.js +61 -0
- package/lib/gsd-headless-config.js +110 -0
- package/lib/gsd-milestone-report.js +323 -0
- package/lib/gsd-state.js +165 -0
- package/lib/gsd-stuck.js +275 -0
- package/lib/gsd-supervisor.js +223 -0
- package/lib/gsd-timing.js +89 -0
- package/lib/gsd.js +908 -16
- package/package.json +1 -1
- package/dist/assets/channel-D_RXsFFT.js +0 -1
- package/dist/assets/classDiagram-4FO5ZUOK-K6wdB4ic.js +0 -1
- package/dist/assets/classDiagram-v2-Q7XG4LA2-K6wdB4ic.js +0 -1
- package/dist/assets/stateDiagram-v2-BHNVJYJU-Cf84VDiH.js +0 -1
package/bin/compose.js
CHANGED
|
@@ -1968,23 +1968,132 @@ if (cmd === 'build') {
|
|
|
1968
1968
|
// compose gsd <feature-code> — runs the per-task fresh-context dispatch
|
|
1969
1969
|
// pipeline (pipelines/gsd.stratum.yaml). Hard-requires existing
|
|
1970
1970
|
// docs/features/<code>/blueprint.md with a parseable Boundary Map.
|
|
1971
|
+
|
|
1972
|
+
// COMP-GSD-6: `compose gsd query <feature>` — instant read-only JSON snapshot
|
|
1973
|
+
// (no LLM/server/Stratum). For status pollers + CI dashboards.
|
|
1974
|
+
if (args[0] === 'query') {
|
|
1975
|
+
const qCode = args.find((a, i) => i > 0 && !a.startsWith('-'))
|
|
1976
|
+
if (!qCode) {
|
|
1977
|
+
console.error('Usage: compose gsd query <feature-code> [--cwd <path>]')
|
|
1978
|
+
process.exit(1)
|
|
1979
|
+
}
|
|
1980
|
+
const { root: qRoot } = resolveCwdWithWorkspace(args.slice(1))
|
|
1981
|
+
const qCwdIdx = args.indexOf('--cwd')
|
|
1982
|
+
const qCwd = qCwdIdx !== -1 ? resolve(args[qCwdIdx + 1]) : qRoot
|
|
1983
|
+
const { buildGsdQuery } = await import('../lib/gsd-state.js')
|
|
1984
|
+
const snapshot = buildGsdQuery(qCwd, qCode)
|
|
1985
|
+
console.log(JSON.stringify(snapshot, null, 2))
|
|
1986
|
+
process.exit(snapshot.status === 'absent' ? 3 : 0)
|
|
1987
|
+
}
|
|
1988
|
+
|
|
1989
|
+
// COMP-GSD-7: `compose gsd report <feature>` — (re)generate the milestone HTML
|
|
1990
|
+
// report from persisted run artifacts (auto-generated on completion; this is
|
|
1991
|
+
// the retroactive/archival path). Writes docs/gsd-reports/<feature>.html.
|
|
1992
|
+
if (args[0] === 'report') {
|
|
1993
|
+
const rCwdIdx = args.indexOf('--cwd')
|
|
1994
|
+
const rCwdValIdx = rCwdIdx !== -1 ? rCwdIdx + 1 : -1
|
|
1995
|
+
const rCode = args.find((a, i) => i > 0 && i !== rCwdValIdx && !a.startsWith('-'))
|
|
1996
|
+
if (!rCode) {
|
|
1997
|
+
console.error('Usage: compose gsd report <feature-code> [--cwd <path>]')
|
|
1998
|
+
process.exit(1)
|
|
1999
|
+
}
|
|
2000
|
+
const { root: rRoot } = resolveCwdWithWorkspace(args.slice(1))
|
|
2001
|
+
const rCwd = rCwdIdx !== -1 ? resolve(args[rCwdIdx + 1]) : rRoot
|
|
2002
|
+
const { generateGsdMilestoneReport } = await import('../lib/gsd-milestone-report.js')
|
|
2003
|
+
const r = generateGsdMilestoneReport(rCode, rCwd)
|
|
2004
|
+
if (!r.ok) {
|
|
2005
|
+
console.error(`gsd report: ${r.error}`)
|
|
2006
|
+
process.exit(1)
|
|
2007
|
+
}
|
|
2008
|
+
console.log(`Milestone report written: ${r.path}`)
|
|
2009
|
+
process.exit(0)
|
|
2010
|
+
}
|
|
2011
|
+
|
|
1971
2012
|
const gsdCode = args.find(a => !a.startsWith('-'))
|
|
2013
|
+
const gsdResume = args.includes('--resume')
|
|
2014
|
+
const gsdResetBudget = args.includes('--reset-budget')
|
|
2015
|
+
const gsdHeadless = args.includes('--headless')
|
|
1972
2016
|
if (!gsdCode) {
|
|
1973
|
-
console.error('Usage: compose gsd <feature-code>')
|
|
2017
|
+
console.error('Usage: compose gsd <feature-code> [--resume] [--reset-budget] [--headless]')
|
|
2018
|
+
console.error(' compose gsd query <feature-code> (instant JSON status snapshot)')
|
|
2019
|
+
console.error(' compose gsd report <feature-code> (generate milestone HTML report)')
|
|
1974
2020
|
console.error('')
|
|
1975
2021
|
console.error('Runs the per-task fresh-context dispatch pipeline (COMP-GSD-2).')
|
|
1976
2022
|
console.error('Hard-requires docs/features/<code>/blueprint.md with a valid Boundary Map.')
|
|
2023
|
+
console.error('Detects stuck tasks (COMP-GSD-5) and halts with a structured diagnostic.')
|
|
2024
|
+
console.error('Enforces budget ceilings (COMP-GSD-4) from .compose/compose.json gsd.budget.*')
|
|
1977
2025
|
console.error('')
|
|
1978
2026
|
console.error('Options:')
|
|
1979
|
-
console.error(' --
|
|
2027
|
+
console.error(' --resume Resume a halted run: re-dispatch the unfinished tasks')
|
|
2028
|
+
console.error(' from .compose/gsd/<code>/pause.json (skips completed tasks).')
|
|
2029
|
+
console.error(' --reset-budget Clear the feature\'s cumulative budget ledger before running')
|
|
2030
|
+
console.error(' (use after raising or removing a spent gsd.budget.cumulative cap).')
|
|
2031
|
+
console.error(' --headless Unattended supervisor (COMP-GSD-6): auto-resume on crash/stuck')
|
|
2032
|
+
console.error(' with backoff + crash recovery. Policy: gsd.headless.* in compose.json.')
|
|
2033
|
+
console.error(' --cwd <path> Working directory (defaults to current)')
|
|
1980
2034
|
process.exit(1)
|
|
1981
2035
|
}
|
|
1982
2036
|
const { root: gsdCwd } = resolveCwdWithWorkspace(args)
|
|
1983
2037
|
const cwdIdx = args.indexOf('--cwd')
|
|
1984
2038
|
const gsdAgentCwd = cwdIdx !== -1 ? resolve(args[cwdIdx + 1]) : gsdCwd
|
|
2039
|
+
|
|
2040
|
+
// COMP-GSD-6: --headless hands off to the supervisor, which spawns plain
|
|
2041
|
+
// `compose gsd` children and auto-resumes per policy. Exit 0 on completion,
|
|
2042
|
+
// non-zero on a terminal stop (failed/fatal/budget/stuck-exhausted/aborted).
|
|
2043
|
+
if (gsdHeadless) {
|
|
2044
|
+
const { runGsdHeadless } = await import('../lib/gsd-supervisor.js')
|
|
2045
|
+
try {
|
|
2046
|
+
const r = await runGsdHeadless(gsdCode, { cwd: gsdAgentCwd, resume: gsdResume })
|
|
2047
|
+
if (r.ok) {
|
|
2048
|
+
console.log(`gsd headless: complete after ${r.attempts} attempt(s).`)
|
|
2049
|
+
process.exit(0)
|
|
2050
|
+
}
|
|
2051
|
+
console.error(`gsd headless: stopped with status "${r.status}" after ${r.attempts} attempt(s).`)
|
|
2052
|
+
console.error(`Query: compose gsd query ${gsdCode}`)
|
|
2053
|
+
process.exit(r.status === 'failed' || r.status === 'fatal' ? 1 : 2)
|
|
2054
|
+
} catch (err) {
|
|
2055
|
+
console.error(`gsd headless failed: ${err.message}`)
|
|
2056
|
+
process.exit(1)
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1985
2060
|
const { runGsd } = await import('../lib/gsd.js')
|
|
1986
2061
|
try {
|
|
1987
|
-
|
|
2062
|
+
if (gsdResetBudget) {
|
|
2063
|
+
// COMP-GSD-4: clear the cumulative ledger so a spent ceiling no longer
|
|
2064
|
+
// refuses the run. Runs before dispatch; per-run windows reset anyway.
|
|
2065
|
+
const { resetGsdUsage } = await import('../lib/budget-ledger.js')
|
|
2066
|
+
resetGsdUsage(resolve(gsdAgentCwd, '.compose'), gsdCode)
|
|
2067
|
+
console.log(`gsd: cleared cumulative budget ledger for ${gsdCode}.`)
|
|
2068
|
+
}
|
|
2069
|
+
const result = await runGsd(gsdCode, { cwd: gsdAgentCwd, resume: gsdResume })
|
|
2070
|
+
if (result.status === 'stuck') {
|
|
2071
|
+
// COMP-GSD-5: a stuck halt is a clean, recoverable stop — not a crash.
|
|
2072
|
+
console.error(`gsd stuck: task ${result.stuckTaskId} tripped the ${result.signal} detector.`)
|
|
2073
|
+
console.error(`Diagnostic: .compose/gsd/${gsdCode}/stuck.md`)
|
|
2074
|
+
console.error(`Resume with: compose gsd ${gsdCode} --resume`)
|
|
2075
|
+
process.exit(2)
|
|
2076
|
+
}
|
|
2077
|
+
if (result.status === 'budget') {
|
|
2078
|
+
// COMP-GSD-4: a budget halt is a clean, recoverable stop — not a crash.
|
|
2079
|
+
if (result.axis === 'cumulative') {
|
|
2080
|
+
console.error(`gsd budget: cumulative ceiling for ${gsdCode} is already spent.`)
|
|
2081
|
+
console.error(`Diagnostic: .compose/gsd/${gsdCode}/budget.md`)
|
|
2082
|
+
console.error(`Raise gsd.budget.cumulative.* or clear it: compose gsd ${gsdCode} --reset-budget`)
|
|
2083
|
+
} else {
|
|
2084
|
+
console.error(`gsd budget: the ${result.axis} ceiling tripped mid-run.`)
|
|
2085
|
+
console.error(`Diagnostic: .compose/gsd/${gsdCode}/budget.md`)
|
|
2086
|
+
console.error(`Raise gsd.budget.* and resume: compose gsd ${gsdCode} --resume`)
|
|
2087
|
+
}
|
|
2088
|
+
process.exit(2)
|
|
2089
|
+
}
|
|
2090
|
+
if (result.status !== 'complete') {
|
|
2091
|
+
// COMP-GSD-6: any non-complete terminal (e.g. a stratum 'killed') is a
|
|
2092
|
+
// failure, not success — don't print "complete" or exit 0.
|
|
2093
|
+
console.error(`gsd ${result.status}: run ended without completing.`)
|
|
2094
|
+
console.error(`Inspect: compose gsd query ${gsdCode}`)
|
|
2095
|
+
process.exit(1)
|
|
2096
|
+
}
|
|
1988
2097
|
console.log(`gsd complete: ${result.blackboardEntries} task results captured.`)
|
|
1989
2098
|
} catch (err) {
|
|
1990
2099
|
console.error(`gsd failed: ${err.message}`)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "gsd-state.json",
|
|
4
|
+
"_source": "COMP-GSD-6",
|
|
5
|
+
"_also": "COMP-GSD-4",
|
|
6
|
+
"_roadmap": "COMP-GSD-6",
|
|
7
|
+
"title": "GsdStateContracts",
|
|
8
|
+
"description": "COMP-GSD-6 run-state artifacts. `state` is the continuously-flushed checkpoint at .compose/gsd/<feature>/state.json — written pre-plan (phase:planning, flowId:null), updated with flowId after stratum.plan, heartbeat-bumped per task event, marked resumeReady once decomposedTasks is populated, and terminally flushed (complete|stuck|budget|failed). A hard crash leaves status:running + a dead pid, which readers derive as 'crashed'. `query` is the read-only envelope emitted by `compose gsd query` — it adds the reader-only derived statuses 'crashed' and 'absent' to the runner taxonomy, plus the advisory heartbeatStale flag. decomposedTasks stores FULL enriched task objects (id/depends_on/description), not ids, so crash-recovery can synthesize a resume graph (it reuses the gsd-stuck.json#/definitions/pause decomposedTasks shape).",
|
|
9
|
+
"definitions": {
|
|
10
|
+
"state": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"title": "GsdRunState",
|
|
13
|
+
"description": "The continuously-flushed run checkpoint. Status is what the RUNNER writes; readers derive 'crashed'/'absent' from this via deriveRunStatus (lib/gsd-state.js).",
|
|
14
|
+
"required": ["feature", "pid", "mode", "phase", "status", "heartbeatAt"],
|
|
15
|
+
"additionalProperties": true,
|
|
16
|
+
"properties": {
|
|
17
|
+
"feature": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"description": "Feature code the gsd run is building (e.g. COMP-GSD-6)."
|
|
20
|
+
},
|
|
21
|
+
"flowId": {
|
|
22
|
+
"type": ["string", "null"],
|
|
23
|
+
"description": "Stratum flow id. null in the pre-plan 'planning' checkpoint; set after stratum.plan returns."
|
|
24
|
+
},
|
|
25
|
+
"pid": {
|
|
26
|
+
"type": "integer",
|
|
27
|
+
"description": "OS pid of the running process. The authoritative crash signal: status:running + a dead pid (process.kill(pid,0) ESRCH) ⇒ crashed."
|
|
28
|
+
},
|
|
29
|
+
"mode": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"const": "gsd",
|
|
32
|
+
"description": "Run mode. Always 'gsd'."
|
|
33
|
+
},
|
|
34
|
+
"phase": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"enum": ["planning", "decompose", "execute", "ship", "done"],
|
|
37
|
+
"description": "Coarse lifecycle phase. 'planning' is the pre-plan checkpoint (before stratum.plan); the failed-vs-fatal boundary is whether this checkpoint exists."
|
|
38
|
+
},
|
|
39
|
+
"status": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"enum": ["running", "complete", "stuck", "budget", "failed"],
|
|
42
|
+
"description": "Runner-written status. 'failed' = an orderly exception AFTER the planning checkpoint (the dispatch-try catch). 'running' persisted with a dead pid is derived as 'crashed' by readers (never self-written)."
|
|
43
|
+
},
|
|
44
|
+
"startedAt": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"format": "date-time",
|
|
47
|
+
"description": "ISO-8601 timestamp the run started."
|
|
48
|
+
},
|
|
49
|
+
"heartbeatAt": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"format": "date-time",
|
|
52
|
+
"description": "ISO-8601 timestamp bumped on every flush AND from the per-task push-event path. A stale heartbeat on a LIVE pid is advisory only (heartbeatStale) — never a crash verdict."
|
|
53
|
+
},
|
|
54
|
+
"headless": {
|
|
55
|
+
"type": "boolean",
|
|
56
|
+
"description": "True if launched under the --headless supervisor."
|
|
57
|
+
},
|
|
58
|
+
"attempt": {
|
|
59
|
+
"type": "integer",
|
|
60
|
+
"minimum": 1,
|
|
61
|
+
"description": "Supervisor attempt counter (resume generation)."
|
|
62
|
+
},
|
|
63
|
+
"resumeReady": {
|
|
64
|
+
"type": "boolean",
|
|
65
|
+
"description": "True once decomposedTasks is flushed post-decompose. Gates the supervisor crash branch: --resume when true, fresh restart when false (crashed during plan/decompose — nothing merged yet)."
|
|
66
|
+
},
|
|
67
|
+
"decomposedTasks": {
|
|
68
|
+
"type": "array",
|
|
69
|
+
"description": "Full enriched task objects (id/depends_on/description, NOT bare ids), so the crash bridge can synthesize a resume graph. Empty until decompose completes.",
|
|
70
|
+
"items": { "type": "object" }
|
|
71
|
+
},
|
|
72
|
+
"completedTaskIds": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"description": "Task ids whose validated result is already merged/in the blackboard. Resume skips these.",
|
|
75
|
+
"items": { "type": "string" }
|
|
76
|
+
},
|
|
77
|
+
"lastTaskId": {
|
|
78
|
+
"type": ["string", "null"],
|
|
79
|
+
"description": "Most recently completed task id (progress hint)."
|
|
80
|
+
},
|
|
81
|
+
"lastStepId": {
|
|
82
|
+
"type": ["string", "null"],
|
|
83
|
+
"description": "Most recent stratum step id."
|
|
84
|
+
},
|
|
85
|
+
"budget": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"description": "Optional mirror of the live flow budget {caps,consumed} for query burn reporting.",
|
|
88
|
+
"additionalProperties": true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"query": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"title": "GsdQuerySnapshot",
|
|
95
|
+
"description": "Read-only envelope emitted by `compose gsd query <feature>`. Synthesized with a fixed source precedence (state.json → pause.json → budget.json → absent). One status vocabulary across query/supervisor/tests.",
|
|
96
|
+
"required": ["feature", "status"],
|
|
97
|
+
"additionalProperties": true,
|
|
98
|
+
"properties": {
|
|
99
|
+
"feature": { "type": "string", "description": "Feature code queried." },
|
|
100
|
+
"status": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"enum": ["running", "crashed", "complete", "stuck", "budget", "failed", "absent"],
|
|
103
|
+
"description": "Derived status. 'crashed' (running+dead-pid) and 'absent' (no run state anywhere) are reader-only; the rest mirror the runner taxonomy."
|
|
104
|
+
},
|
|
105
|
+
"phase": {
|
|
106
|
+
"type": ["string", "null"],
|
|
107
|
+
"enum": ["planning", "decompose", "execute", "ship", "done", null]
|
|
108
|
+
},
|
|
109
|
+
"heartbeatStale": {
|
|
110
|
+
"type": "boolean",
|
|
111
|
+
"description": "Advisory: status is 'running' on a live pid but the heartbeat exceeded the stale threshold (run may be wedged). Never implies crashed."
|
|
112
|
+
},
|
|
113
|
+
"progress": {
|
|
114
|
+
"type": "object",
|
|
115
|
+
"description": "Task progress.",
|
|
116
|
+
"properties": {
|
|
117
|
+
"completed": { "type": "integer", "minimum": 0 },
|
|
118
|
+
"total": { "type": "integer", "minimum": 0 }
|
|
119
|
+
},
|
|
120
|
+
"additionalProperties": false
|
|
121
|
+
},
|
|
122
|
+
"resumeReady": {
|
|
123
|
+
"type": "boolean",
|
|
124
|
+
"description": "Whether a crash here would --resume (true) or restart fresh (false)."
|
|
125
|
+
},
|
|
126
|
+
"pid": { "type": ["integer", "null"] },
|
|
127
|
+
"flowId": { "type": ["string", "null"] },
|
|
128
|
+
"pause": {
|
|
129
|
+
"type": ["object", "null"],
|
|
130
|
+
"description": "When paused: the pause kind + detail (from pause.json)."
|
|
131
|
+
},
|
|
132
|
+
"budget": {
|
|
133
|
+
"type": ["object", "null"],
|
|
134
|
+
"description": "Cumulative/flow budget info when available (from state.json or budget.json)."
|
|
135
|
+
},
|
|
136
|
+
"heartbeatAt": { "type": ["string", "null"], "format": "date-time" }
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -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
|
+
}
|