@tekyzinc/gsd-t 3.29.10 → 4.0.12
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/CHANGELOG.md +176 -0
- package/bin/gsd-t-parallel.cjs +64 -13
- package/bin/gsd-t-test-data-adapters/file-json-array.cjs +27 -3
- package/bin/gsd-t-test-data-adapters/localstorage-key-prefix.cjs +6 -1
- package/bin/gsd-t-test-data-adapters/sqlite-table-where.cjs +16 -1
- package/bin/gsd-t-test-data-ledger.cjs +1 -0
- package/bin/gsd-t.js +48 -376
- package/bin/journey-coverage.cjs +6 -3
- package/bin/parallel-cli.cjs +13 -1
- package/commands/gsd-t-debug.md +47 -533
- package/commands/gsd-t-design-decompose.md +24 -497
- package/commands/gsd-t-doc-ripple.md +23 -139
- package/commands/gsd-t-execute.md +41 -958
- package/commands/gsd-t-help.md +0 -2
- package/commands/gsd-t-impact.md +26 -295
- package/commands/gsd-t-integrate.md +44 -369
- package/commands/gsd-t-milestone.md +25 -124
- package/commands/gsd-t-partition.md +28 -539
- package/commands/gsd-t-plan.md +26 -472
- package/commands/gsd-t-prd.md +25 -311
- package/commands/gsd-t-quick.md +1 -1
- package/commands/gsd-t-resume.md +0 -33
- package/commands/gsd-t-verify.md +52 -569
- package/commands/gsd-t-wave.md +41 -430
- package/package.json +1 -1
- package/scripts/gsd-t-calibration-hook.js +3 -1
- package/scripts/hooks/pre-commit-journey-coverage +0 -2
- package/scripts/hooks/pre-commit-playwright-gate +0 -2
- package/templates/CLAUDE-global.md +43 -173
- package/templates/CLAUDE-project.md +118 -104
- package/templates/prompts/design-verify-subagent.md +3 -0
- package/templates/prompts/qa-subagent.md +3 -0
- package/templates/prompts/red-team-subagent.md +5 -2
- package/templates/workflows/_lib.js +176 -0
- package/templates/workflows/gsd-t-debug.workflow.js +86 -0
- package/templates/workflows/gsd-t-execute.workflow.js +206 -0
- package/templates/workflows/gsd-t-integrate.workflow.js +71 -0
- package/templates/workflows/gsd-t-phase.workflow.js +94 -0
- package/templates/workflows/gsd-t-quick.workflow.js +72 -0
- package/templates/workflows/gsd-t-verify.workflow.js +348 -0
- package/templates/workflows/gsd-t-wave.workflow.js +43 -0
- package/bin/check-headless-sessions.js +0 -140
- package/bin/context-budget-audit.cjs +0 -447
- package/bin/context-meter-config.cjs +0 -101
- package/bin/context-meter-config.test.cjs +0 -101
- package/bin/event-stream.cjs +0 -205
- package/bin/gsd-t-benchmark-orchestrator.js +0 -437
- package/bin/gsd-t-capture-lint.cjs +0 -440
- package/bin/gsd-t-economics.cjs +0 -315
- package/bin/gsd-t-in-session-usage.cjs +0 -213
- package/bin/gsd-t-orchestrator-config.cjs +0 -161
- package/bin/gsd-t-orchestrator-queue.cjs +0 -180
- package/bin/gsd-t-orchestrator-recover.cjs +0 -231
- package/bin/gsd-t-orchestrator-worker.cjs +0 -219
- package/bin/gsd-t-orchestrator.js +0 -535
- package/bin/gsd-t-parallel-probe.cjs +0 -132
- package/bin/gsd-t-ratelimit-probe-worker.cjs +0 -237
- package/bin/gsd-t-ratelimit-probe.cjs +0 -648
- package/bin/gsd-t-report-tokens.cjs +0 -549
- package/bin/gsd-t-stream-feed-client.cjs +0 -151
- package/bin/gsd-t-token-backfill.cjs +0 -366
- package/bin/gsd-t-token-capture.cjs +0 -321
- package/bin/gsd-t-token-dashboard.cjs +0 -353
- package/bin/gsd-t-token-regenerate-log.cjs +0 -129
- package/bin/gsd-t-tool-attribution.cjs +0 -377
- package/bin/gsd-t-tool-cost.cjs +0 -195
- package/bin/gsd-t-transcript-tee.cjs +0 -246
- package/bin/gsd-t-unattended-heartbeat.cjs +0 -188
- package/bin/gsd-t-unattended-platform.cjs +0 -551
- package/bin/gsd-t-unattended-platform.js +0 -381
- package/bin/gsd-t-unattended-safety.cjs +0 -773
- package/bin/gsd-t-unattended-safety.js +0 -788
- package/bin/gsd-t-unattended.cjs +0 -2109
- package/bin/gsd-t-unattended.js +0 -1367
- package/bin/gsd-t-worker-dispatch.cjs +0 -211
- package/bin/handoff-lock.cjs +0 -249
- package/bin/handoff-lock.js +0 -249
- package/bin/headless-auto-spawn.cjs +0 -711
- package/bin/headless-auto-spawn.js +0 -373
- package/bin/headless-exit-codes.cjs +0 -67
- package/bin/live-activity-report.cjs +0 -615
- package/bin/log-tail.cjs +0 -81
- package/bin/m44-proof-measure.cjs +0 -285
- package/bin/m46-iter-proof.cjs +0 -149
- package/bin/m46-worker-proof.cjs +0 -201
- package/bin/m55-substrate-proof.cjs +0 -134
- package/bin/metrics-rollup.js +0 -200
- package/bin/model-windows.cjs +0 -99
- package/bin/model-windows.test.cjs +0 -75
- package/bin/parallelism-report.cjs +0 -535
- package/bin/runway-estimator.cjs +0 -242
- package/bin/spawn-plan-derive.cjs +0 -163
- package/bin/spawn-plan-status-updater.cjs +0 -292
- package/bin/spawn-plan-writer.cjs +0 -204
- package/bin/supervisor-pid-fingerprint.cjs +0 -126
- package/bin/token-budget.cjs +0 -265
- package/bin/unattended-watch-format.cjs +0 -178
- package/bin/watch-progress.js +0 -155
- package/commands/gsd-t-unattended-stop.md +0 -85
- package/commands/gsd-t-unattended-watch.md +0 -465
- package/commands/gsd-t-unattended.md +0 -476
- package/commands/gsd-t-visualize.md +0 -116
- package/scripts/gsd-t-context-meter.e2e.test.js +0 -364
- package/scripts/gsd-t-context-meter.js +0 -341
- package/scripts/gsd-t-context-meter.test.js +0 -471
- package/scripts/gsd-t-dashboard-server.js +0 -1203
- package/scripts/gsd-t-post-commit-spawn-plan.sh +0 -86
- package/scripts/gsd-t-transcript.html +0 -1821
- package/scripts/hooks/gsd-t-conversation-capture.js +0 -439
- package/scripts/hooks/gsd-t-in-session-usage-hook.js +0 -84
- package/scripts/spawn-plan-fmt-tokens.cjs +0 -80
- package/templates/hooks/post-commit-spawn-plan.sh +0 -85
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bin/unattended-watch-format.cjs
|
|
3
|
-
*
|
|
4
|
-
* Pure formatter for the unattended watch-tick activity block (M38 ES).
|
|
5
|
-
* Given an array of events (as returned by event-stream.readSinceCursor) and
|
|
6
|
-
* supervisor state, render the human-scannable output per
|
|
7
|
-
* `.gsd-t/contracts/unattended-event-stream-contract.md` §4.
|
|
8
|
-
*
|
|
9
|
-
* Kept separate from the watch command so it can be unit-tested (ES-T3) and
|
|
10
|
-
* so the command itself stays a thin shell wrapper.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
"use strict";
|
|
14
|
-
|
|
15
|
-
function _groupByIter(events) {
|
|
16
|
-
const groups = new Map();
|
|
17
|
-
for (const ev of events) {
|
|
18
|
-
if (!ev || typeof ev !== "object") continue;
|
|
19
|
-
const iter = Number.isFinite(ev.iter) ? ev.iter : 0;
|
|
20
|
-
if (!groups.has(iter)) groups.set(iter, []);
|
|
21
|
-
groups.get(iter).push(ev);
|
|
22
|
-
}
|
|
23
|
-
return [...groups.entries()].sort((a, b) => a[0] - b[0]);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function _fmtElapsed(startedAt, now) {
|
|
27
|
-
if (!startedAt) return "";
|
|
28
|
-
const t = Date.parse(startedAt);
|
|
29
|
-
if (!Number.isFinite(t)) return "";
|
|
30
|
-
const ms = (now || Date.now()) - t;
|
|
31
|
-
const m = Math.floor(ms / 60000);
|
|
32
|
-
if (m < 60) return `+${m}m elapsed`;
|
|
33
|
-
const h = Math.floor(m / 60);
|
|
34
|
-
return `+${h}h${m % 60}m elapsed`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function _fmtDuration(s) {
|
|
38
|
-
if (!Number.isFinite(s) || s < 0) return "";
|
|
39
|
-
if (s < 60) return `${s}s`;
|
|
40
|
-
const m = Math.floor(s / 60);
|
|
41
|
-
return `${m}m ${s % 60}s`;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function _formatIteration(iter, evs) {
|
|
45
|
-
const taskStart = evs.find((e) => e.type === "task_start");
|
|
46
|
-
const taskComplete = evs.find((e) => e.type === "task_complete");
|
|
47
|
-
const fileChanges = evs.filter((e) => e.type === "file_changed");
|
|
48
|
-
const testResults = evs.filter((e) => e.type === "test_result");
|
|
49
|
-
const verdicts = evs.filter((e) => e.type === "subagent_verdict");
|
|
50
|
-
const errors = evs.filter((e) => e.type === "error");
|
|
51
|
-
const retries = evs.filter((e) => e.type === "retry");
|
|
52
|
-
|
|
53
|
-
const lines = [];
|
|
54
|
-
if (taskStart) {
|
|
55
|
-
const wave = taskStart.wave ? ` (wave ${taskStart.wave})` : "";
|
|
56
|
-
const task = taskStart.task || "(unnamed)";
|
|
57
|
-
lines.push(` ▶ task: ${task}${wave}`);
|
|
58
|
-
}
|
|
59
|
-
if (fileChanges.length > 0) {
|
|
60
|
-
const paths = fileChanges.map((e) => e.path || "?").filter(Boolean);
|
|
61
|
-
const shown = paths.slice(0, 5).join(", ");
|
|
62
|
-
const extra = paths.length > 5 ? ` … and ${paths.length - 5} more` : "";
|
|
63
|
-
lines.push(` 📝 ${paths.length} file${paths.length === 1 ? "" : "s"} modified (${shown}${extra})`);
|
|
64
|
-
}
|
|
65
|
-
for (const tr of testResults) {
|
|
66
|
-
const suite = tr.suite || "?";
|
|
67
|
-
const pass = Number.isFinite(tr.pass) ? tr.pass : 0;
|
|
68
|
-
const total = Number.isFinite(tr.total) ? tr.total : 0;
|
|
69
|
-
const mark = (tr.fail || 0) > 0 ? "❌" : "✅";
|
|
70
|
-
lines.push(` ${mark} test_result: ${suite} ${pass}/${total} pass`);
|
|
71
|
-
}
|
|
72
|
-
for (const v of verdicts) {
|
|
73
|
-
const agent = v.agent || "?";
|
|
74
|
-
const verdict = v.verdict || "?";
|
|
75
|
-
const findings = Number.isFinite(v.findings_count) ? ` (${v.findings_count} findings)` : "";
|
|
76
|
-
const mark = verdict === "pass" || verdict === "grudging_pass" ? "✅" : "❌";
|
|
77
|
-
lines.push(` ${mark} subagent_verdict: ${agent} ${verdict}${findings}`);
|
|
78
|
-
}
|
|
79
|
-
for (const er of errors) {
|
|
80
|
-
lines.push(` ❌ error: ${er.error || "(unknown)"}${er.recoverable ? " [recoverable]" : ""}`);
|
|
81
|
-
}
|
|
82
|
-
for (const r of retries) {
|
|
83
|
-
lines.push(` 🔁 retry: attempt ${r.attempt || iter} — ${r.reason || "?"}`);
|
|
84
|
-
}
|
|
85
|
-
if (taskComplete) {
|
|
86
|
-
const dur = _fmtDuration(taskComplete.duration_s);
|
|
87
|
-
const verdict = taskComplete.verdict || "?";
|
|
88
|
-
lines.push(` ⏱ duration: ${dur} · verdict: ${verdict}`);
|
|
89
|
-
}
|
|
90
|
-
return lines;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Format an activity block for the watch tick.
|
|
95
|
-
*
|
|
96
|
-
* @param {object} args
|
|
97
|
-
* @param {object[]} args.events — events since last cursor (may be empty)
|
|
98
|
-
* @param {object} args.state — supervisor state.json contents
|
|
99
|
-
* @param {number} [args.now] — override for Date.now() (tests)
|
|
100
|
-
* @returns {string}
|
|
101
|
-
*/
|
|
102
|
-
function _renderWatchProgressTree(args) {
|
|
103
|
-
// M39 D2 — best-effort watch-progress tree appended below the formatter
|
|
104
|
-
// output. Banner preserved verbatim; returns "" on any failure so callers
|
|
105
|
-
// never see a tree when state is missing/unavailable.
|
|
106
|
-
try {
|
|
107
|
-
const wp = require("./watch-progress.js");
|
|
108
|
-
const stateDir = (args && args.stateDir) || ".gsd-t/.watch-state";
|
|
109
|
-
const current = args && args.currentAgent ? args.currentAgent : null;
|
|
110
|
-
const tree = wp.buildTree(stateDir);
|
|
111
|
-
return wp.renderTree(tree, { currentAgent: current }) || "";
|
|
112
|
-
} catch (_) {
|
|
113
|
-
return "";
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function formatWatchTick(args) {
|
|
118
|
-
const events = Array.isArray(args && args.events) ? args.events : [];
|
|
119
|
-
const state = (args && args.state) || {};
|
|
120
|
-
const now = args && Number.isFinite(args.now) ? args.now : Date.now();
|
|
121
|
-
|
|
122
|
-
const elapsed = _fmtElapsed(state.startedAt, now);
|
|
123
|
-
const iter = Number.isFinite(state.iter) ? state.iter : 0;
|
|
124
|
-
const header = `[unattended supervisor — iter ${iter}${elapsed ? `, ${elapsed}` : ""}]`;
|
|
125
|
-
|
|
126
|
-
let body;
|
|
127
|
-
if (events.length === 0) {
|
|
128
|
-
body = `${header} (no new activity since last tick)`;
|
|
129
|
-
} else {
|
|
130
|
-
const groups = _groupByIter(events);
|
|
131
|
-
const blocks = [];
|
|
132
|
-
for (const [groupIter, evs] of groups) {
|
|
133
|
-
const groupHeader = groups.length > 1 ? `[iter ${groupIter}]` : header;
|
|
134
|
-
const bodyLines = _formatIteration(groupIter, evs);
|
|
135
|
-
blocks.push([groupHeader, ...bodyLines].join("\n"));
|
|
136
|
-
}
|
|
137
|
-
body = groups.length > 1 ? `${header}\n${blocks.join("\n")}` : blocks[0];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// M39 D2 — watch-progress tree below the existing formatter output.
|
|
141
|
-
// Banner preserved verbatim; tree appended only when caller opts in via
|
|
142
|
-
// `args.withWatchProgress = true`. Default-off preserves backward
|
|
143
|
-
// compatibility with existing snapshot-style callers (tests).
|
|
144
|
-
if (!(args && args.withWatchProgress === true)) return body;
|
|
145
|
-
const tree = _renderWatchProgressTree({
|
|
146
|
-
stateDir: args && args.stateDir,
|
|
147
|
-
currentAgent: args && (args.currentAgent || state.sessionId),
|
|
148
|
-
});
|
|
149
|
-
return tree ? `${body}\n${tree}` : body;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Terminal-status block when supervisor reached `done`/`failed`/`stopped`/`crashed`.
|
|
154
|
-
* Returns null if status is not terminal.
|
|
155
|
-
*/
|
|
156
|
-
function formatTerminalBlock(state) {
|
|
157
|
-
if (!state || typeof state !== "object") return null;
|
|
158
|
-
const s = state.status;
|
|
159
|
-
if (s === "done") {
|
|
160
|
-
return `🎉 Unattended supervisor COMPLETED the milestone (${state.milestone || "?"}, iter ${state.iter || 0}).`;
|
|
161
|
-
}
|
|
162
|
-
if (s === "failed") {
|
|
163
|
-
return `❌ Unattended supervisor HALTED — status=failed (last exit ${state.lastExit ?? "?"}, iter ${state.iter || 0}).`;
|
|
164
|
-
}
|
|
165
|
-
if (s === "stopped") {
|
|
166
|
-
return `🛑 Unattended supervisor STOPPED by user (iter ${state.iter || 0}).`;
|
|
167
|
-
}
|
|
168
|
-
if (s === "crashed") {
|
|
169
|
-
return `💥 Unattended supervisor CRASHED (iter ${state.iter || 0}).`;
|
|
170
|
-
}
|
|
171
|
-
return null;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
module.exports = {
|
|
175
|
-
formatWatchTick,
|
|
176
|
-
formatTerminalBlock,
|
|
177
|
-
_internal: { _groupByIter, _formatIteration, _fmtElapsed, _fmtDuration },
|
|
178
|
-
};
|
package/bin/watch-progress.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* bin/watch-progress.js — GSD-T Watch-Progress Tree Builder + Renderer
|
|
4
|
-
*
|
|
5
|
-
* Reads `.gsd-t/.watch-state/*.json` state files written by
|
|
6
|
-
* `scripts/gsd-t-watch-state.js` and reconstructs the agent workflow tree
|
|
7
|
-
* via `parent_agent_id` lineage. Renders a task-list with ✅/🔄/⬜ markers
|
|
8
|
-
* below every `--watch` surface (banner preserved intact).
|
|
9
|
-
*
|
|
10
|
-
* Contract: `.gsd-t/contracts/watch-progress-contract.md` v1.0.0
|
|
11
|
-
* Owner: D2 (M39 `d2-progress-watch`)
|
|
12
|
-
* Zero external deps.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
"use strict";
|
|
16
|
-
|
|
17
|
-
const fs = require("fs");
|
|
18
|
-
const path = require("path");
|
|
19
|
-
|
|
20
|
-
const STALE_MS = 24 * 60 * 60 * 1000; // 24h
|
|
21
|
-
|
|
22
|
-
const MARKERS = {
|
|
23
|
-
done: "✅",
|
|
24
|
-
in_progress: "🔄",
|
|
25
|
-
pending: "⬜",
|
|
26
|
-
skipped: "➡️",
|
|
27
|
-
failed: "❌",
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function _readAll(stateDir) {
|
|
31
|
-
if (!stateDir || !fs.existsSync(stateDir)) return [];
|
|
32
|
-
let entries;
|
|
33
|
-
try {
|
|
34
|
-
entries = fs.readdirSync(stateDir);
|
|
35
|
-
} catch (_) {
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
38
|
-
const records = [];
|
|
39
|
-
for (const f of entries) {
|
|
40
|
-
if (!f.endsWith(".json")) continue;
|
|
41
|
-
try {
|
|
42
|
-
const raw = fs.readFileSync(path.join(stateDir, f), "utf8");
|
|
43
|
-
const rec = JSON.parse(raw);
|
|
44
|
-
if (rec && typeof rec === "object" && typeof rec.agent_id === "string") {
|
|
45
|
-
records.push(rec);
|
|
46
|
-
}
|
|
47
|
-
} catch (_) { /* skip malformed */ }
|
|
48
|
-
}
|
|
49
|
-
return records;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function _isStale(record, now) {
|
|
53
|
-
if (!record.completed_at) return false;
|
|
54
|
-
const t = Date.parse(record.completed_at);
|
|
55
|
-
if (!Number.isFinite(t)) return false;
|
|
56
|
-
return (now - t) > STALE_MS;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function buildTree(stateDir, options) {
|
|
60
|
-
const now = options && Number.isFinite(options.now) ? options.now : Date.now();
|
|
61
|
-
const records = _readAll(stateDir).filter((r) => !_isStale(r, now));
|
|
62
|
-
const index = new Map();
|
|
63
|
-
for (const r of records) index.set(r.agent_id, { record: r, children: [] });
|
|
64
|
-
const roots = [];
|
|
65
|
-
const orphans = [];
|
|
66
|
-
for (const r of records) {
|
|
67
|
-
const node = index.get(r.agent_id);
|
|
68
|
-
const parentId = r.parent_agent_id;
|
|
69
|
-
if (parentId === null || parentId === undefined) {
|
|
70
|
-
roots.push(node);
|
|
71
|
-
} else if (index.has(parentId)) {
|
|
72
|
-
index.get(parentId).children.push(node);
|
|
73
|
-
} else {
|
|
74
|
-
orphans.push(node);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return { roots, orphans };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function _marker(status) {
|
|
81
|
-
return MARKERS[status] || MARKERS.pending;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function _countDone(node) {
|
|
85
|
-
let count = 0;
|
|
86
|
-
const s = node.record.status;
|
|
87
|
-
if (s === "done" || s === "skipped") count = 1;
|
|
88
|
-
for (const c of node.children) count += _countDone(c);
|
|
89
|
-
return count;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function _flattenIds(node, acc) {
|
|
93
|
-
acc.push(node.record.agent_id);
|
|
94
|
-
for (const c of node.children) _flattenIds(c, acc);
|
|
95
|
-
return acc;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function _containsAgent(node, agentId) {
|
|
99
|
-
if (!agentId) return false;
|
|
100
|
-
return _flattenIds(node, []).includes(agentId);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function _pickCurrent(tree) {
|
|
104
|
-
let pick = null;
|
|
105
|
-
let latest = -Infinity;
|
|
106
|
-
const walk = (node) => {
|
|
107
|
-
const r = node.record;
|
|
108
|
-
if (r.status === "in_progress" && r.started_at) {
|
|
109
|
-
const t = Date.parse(r.started_at);
|
|
110
|
-
if (Number.isFinite(t) && t > latest) { latest = t; pick = r.agent_id; }
|
|
111
|
-
}
|
|
112
|
-
for (const c of node.children) walk(c);
|
|
113
|
-
};
|
|
114
|
-
for (const root of tree.roots) walk(root);
|
|
115
|
-
return pick;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function _renderExpanded(node, depth, lines) {
|
|
119
|
-
const indent = " ".repeat(depth);
|
|
120
|
-
const r = node.record;
|
|
121
|
-
lines.push(`${indent}${_marker(r.status)} ${r.step_label || r.command || r.agent_id}`);
|
|
122
|
-
for (const c of node.children) _renderExpanded(c, depth + 1, lines);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function _renderCollapsed(node, depth, lines) {
|
|
126
|
-
const indent = " ".repeat(depth);
|
|
127
|
-
const r = node.record;
|
|
128
|
-
const doneCount = _countDone(node);
|
|
129
|
-
const label = r.step_label || r.command || r.agent_id;
|
|
130
|
-
lines.push(`${indent}${_marker(r.status)} ${label} (${doneCount} tasks done)`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function renderTree(tree, options) {
|
|
134
|
-
if (!tree || (tree.roots.length === 0 && tree.orphans.length === 0)) return "";
|
|
135
|
-
const opts = options || {};
|
|
136
|
-
const currentAgent = opts.currentAgent || _pickCurrent(tree);
|
|
137
|
-
const lines = [];
|
|
138
|
-
for (const root of tree.roots) {
|
|
139
|
-
if (_containsAgent(root, currentAgent)) _renderExpanded(root, 0, lines);
|
|
140
|
-
else _renderCollapsed(root, 0, lines);
|
|
141
|
-
}
|
|
142
|
-
if (tree.orphans.length > 0) {
|
|
143
|
-
lines.push("⬜ (orphan subtree — parent state missing)");
|
|
144
|
-
for (const o of tree.orphans) _renderExpanded(o, 1, lines);
|
|
145
|
-
}
|
|
146
|
-
return lines.join("\n");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
module.exports = { buildTree, renderTree, MARKERS, STALE_MS, _pickCurrent };
|
|
150
|
-
|
|
151
|
-
if (require.main === module) {
|
|
152
|
-
const stateDir = process.argv[2] || path.join(".gsd-t", ".watch-state");
|
|
153
|
-
const out = renderTree(buildTree(stateDir));
|
|
154
|
-
if (out) process.stdout.write(out + "\n");
|
|
155
|
-
}
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
# GSD-T: Unattended Stop — Signal Supervisor to Halt
|
|
2
|
-
|
|
3
|
-
**Model**: haiku (trivial sentinel toucher — no reasoning needed)
|
|
4
|
-
|
|
5
|
-
You are signaling the unattended supervisor to halt cleanly between worker iterations by writing the stop sentinel file at `.gsd-t/.unattended/stop`. This is a fire-and-forget command: no PID kill, no wait, no confirmation prompt.
|
|
6
|
-
|
|
7
|
-
See `unattended-supervisor-contract.md` §10 (Stop Mechanism) for the full handshake.
|
|
8
|
-
|
|
9
|
-
## Step 1: Check Supervisor State Directory Exists
|
|
10
|
-
|
|
11
|
-
Run via Bash:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
if [ ! -d ".gsd-t/.unattended" ]; then
|
|
15
|
-
echo "No supervisor state directory — nothing to stop."
|
|
16
|
-
exit 0
|
|
17
|
-
fi
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
If the directory does not exist, exit 0 (not an error — there is simply nothing to signal).
|
|
21
|
-
|
|
22
|
-
## Step 2: Check Supervisor PID File Exists
|
|
23
|
-
|
|
24
|
-
Run via Bash:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
if [ ! -f ".gsd-t/.unattended/supervisor.pid" ]; then
|
|
28
|
-
echo "No supervisor running in this project (no PID file at .gsd-t/.unattended/supervisor.pid)."
|
|
29
|
-
exit 0
|
|
30
|
-
fi
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
If the PID file is missing, the supervisor has already finalized cleanly. Exit 0 without writing the sentinel.
|
|
34
|
-
|
|
35
|
-
## Step 3: Read Current Session Snapshot
|
|
36
|
-
|
|
37
|
-
Run via Bash to grab the session ID and current iteration from `state.json` (best-effort — tolerate missing or partial state):
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
node -e "
|
|
41
|
-
try {
|
|
42
|
-
const s = JSON.parse(require('fs').readFileSync('.gsd-t/.unattended/state.json', 'utf8'));
|
|
43
|
-
console.log('SESSION=' + (s.sessionId || 'unknown'));
|
|
44
|
-
console.log('ITER=' + (s.iter ?? 'unknown'));
|
|
45
|
-
console.log('STATUS=' + (s.status || 'unknown'));
|
|
46
|
-
} catch (e) {
|
|
47
|
-
console.log('SESSION=unknown');
|
|
48
|
-
console.log('ITER=unknown');
|
|
49
|
-
console.log('STATUS=unknown');
|
|
50
|
-
}
|
|
51
|
-
"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Step 4: Write the Stop Sentinel
|
|
55
|
-
|
|
56
|
-
Write `.gsd-t/.unattended/stop` containing the current ISO timestamp as the body (for diagnostics — the supervisor only checks for the file's existence, not its contents):
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
node -e "require('fs').writeFileSync('.gsd-t/.unattended/stop', new Date().toISOString())"
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
This is race-free, terminal-close-safe, and language-agnostic (per contract §10).
|
|
63
|
-
|
|
64
|
-
## Step 5: Print Confirmation
|
|
65
|
-
|
|
66
|
-
Output the confirmation block:
|
|
67
|
-
|
|
68
|
-
```
|
|
69
|
-
🛑 Stop sentinel written. Supervisor will halt after the current worker finishes (up to ~5 min).
|
|
70
|
-
|
|
71
|
-
Session: {SESSION from Step 3}
|
|
72
|
-
Iter: {ITER from Step 3}
|
|
73
|
-
Status: {STATUS from Step 3}
|
|
74
|
-
|
|
75
|
-
The current worker runs to completion. Stop is honored at the next pre-worker checkpoint.
|
|
76
|
-
The watch loop (if running) will detect status=stopped on its next tick and stop itself.
|
|
77
|
-
If a watch tick fires before the supervisor processes the sentinel, it may reschedule once
|
|
78
|
-
more — this is expected. It will catch the terminal status on the following tick.
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Step 6: Return Immediately
|
|
82
|
-
|
|
83
|
-
Do NOT wait for the supervisor to acknowledge. Do NOT poll state.json. Do NOT call ScheduleWakeup. The supervisor will finalize state.json with `status=stopped` on its next iteration check — that's the watch loop's job to observe, not this command's.
|
|
84
|
-
|
|
85
|
-
$ARGUMENTS
|