@tekyzinc/gsd-t 4.7.10 → 4.7.11
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 +10 -0
- package/README.md +1 -1
- package/bin/gsd-t-archive-domains.cjs +136 -0
- package/bin/gsd-t.js +19 -0
- package/commands/gsd-t-complete-milestone.md +15 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [4.7.11] - 2026-06-22 (backlog #40 — deterministic domain archive+sweep — patch)
|
|
6
|
+
|
|
7
|
+
### Fixed — complete-milestone now deterministically archives + sweeps a milestone's domains
|
|
8
|
+
|
|
9
|
+
`complete-milestone` Step 7 was prose-only ("archive domains → clear `.gsd-t/domains/`") with no enforcement, so a Level-3 autonomous agent skipped or partial-did it for ~30 milestones — accumulating 77 stale domain dirs that polluted the file-disjointness oracle (surfaced + manually pruned during M90). Root-cause fix:
|
|
10
|
+
|
|
11
|
+
- **`bin/gsd-t-archive-domains.cjs`** (new) + `gsd-t archive-domains` dispatch: copies an EXPLICIT set of the completing milestone's domain dirs → `<archive>/domains/<name>/`, then removes them from `.gsd-t/domains/`. Idempotent (re-running is a no-op), containment-guarded (refuses any name with path separators / dot-segments or resolving outside `.gsd-t/domains/` — [[feedback_destructive_path_ops_containment]]), fail-closed (a bad name aborts the whole batch, no partial sweep). Domains of other still-active milestones are left untouched (not a blanket wipe).
|
|
12
|
+
- `commands/gsd-t-complete-milestone.md` Step 7: prose → the deterministic helper call.
|
|
13
|
+
- Propagated via PROJECT_BIN_TOOLS + GLOBAL_BIN_TOOLS. 5 tests (sweep-exactly / idempotent / dry-run / containment / bad-input). Suite 2003 / 1999 pass / 0 fail.
|
|
14
|
+
|
|
5
15
|
## [4.7.10] - 2026-06-22 (M90 — The Unproven-Assumption Doctrine — minor)
|
|
6
16
|
|
|
7
17
|
### Added — a self-governing doctrine that stops the system building on unproven assumptions
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GSD-T: Contract-Driven Development for Claude Code
|
|
2
2
|
|
|
3
|
-
**v4.7.
|
|
3
|
+
**v4.7.11** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
|
|
4
4
|
|
|
5
5
|
**Eliminates context rot** — task-level fresh dispatch (one subagent per task, ~10-20% context each) means compaction never triggers.
|
|
6
6
|
**Compaction-proof debug loops** — `gsd-t headless --debug-loop` runs test-fix-retest cycles as separate `claude -p` sessions. A JSONL debug ledger persists all hypothesis/fix/learning history across fresh sessions. Anti-repetition preamble injection prevents retrying failed hypotheses. Escalation tiers (sonnet → opus → human) and a hard iteration ceiling enforced externally.
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gsd-t-archive-domains.cjs
|
|
3
|
+
*
|
|
4
|
+
* Backlog #40 — Deterministic archive+sweep of a completed milestone's domain dirs.
|
|
5
|
+
*
|
|
6
|
+
* complete-milestone Step 7 was prose-only ("archive domains → clear .gsd-t/domains/") with no
|
|
7
|
+
* enforcement, so a Level-3 agent skipped/partial-did the clear for ~30 milestones and 77 stale
|
|
8
|
+
* domain dirs accumulated, polluting the file-disjointness oracle. This helper makes the sweep a
|
|
9
|
+
* deterministic, idempotent, containment-guarded operation.
|
|
10
|
+
*
|
|
11
|
+
* Behavior — for an EXPLICIT set of the completing milestone's domains (NOT a blanket wipe; a
|
|
12
|
+
* later still-active milestone may legitimately have live domains, e.g. M90 completing while
|
|
13
|
+
* M87/M88 are queued):
|
|
14
|
+
* 1. Copy each domain dir → <archiveDir>/domains/<name>/ (skip if already archived — idempotent)
|
|
15
|
+
* 2. Remove it from .gsd-t/domains/ (skip if already gone — idempotent)
|
|
16
|
+
* Domains NOT in the set are left untouched.
|
|
17
|
+
*
|
|
18
|
+
* Containment guard ([[feedback_destructive_path_ops_containment]]): every removed path MUST resolve
|
|
19
|
+
* INSIDE .gsd-t/domains/ AND NOT equal it. Predicate: resolved.startsWith(domainsRoot + sep) &&
|
|
20
|
+
* resolved !== domainsRoot. Any violation aborts the whole run (fail-closed, no partial sweep).
|
|
21
|
+
*
|
|
22
|
+
* House style: { ok:true, ... } | { ok:false, error }; bad input → non-zero CLI exit; Node
|
|
23
|
+
* built-ins only; sync APIs; zero deps.
|
|
24
|
+
*
|
|
25
|
+
* Usage:
|
|
26
|
+
* gsd-t archive-domains --domains d-a,d-b,d-c --archive .gsd-t/milestones/mNN-name-DATE [--projectDir .] [--dry-run] [--json]
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
'use strict';
|
|
30
|
+
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
|
|
34
|
+
const DOMAINS_SUBPATH = path.join('.gsd-t', 'domains');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Archive + sweep the named domains.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} opts
|
|
40
|
+
* @param {string[]} opts.domains — explicit domain dir names (the completing milestone's set)
|
|
41
|
+
* @param {string} opts.archiveDir — milestone archive dir (domains land under <archiveDir>/domains/)
|
|
42
|
+
* @param {string} [opts.projectDir] — project root (default cwd)
|
|
43
|
+
* @param {boolean} [opts.dryRun] — compute the plan, write nothing
|
|
44
|
+
* @returns {{ok:true, archived:string[], removed:string[], skipped:string[], dryRun:boolean}
|
|
45
|
+
* | {ok:false, error:string}}
|
|
46
|
+
*/
|
|
47
|
+
function archiveDomains({ domains, archiveDir, projectDir, dryRun = false } = {}) {
|
|
48
|
+
if (!Array.isArray(domains) || domains.length === 0) {
|
|
49
|
+
return { ok: false, error: 'archive-domains requires a non-empty --domains list (the completing milestone\'s domain set)' };
|
|
50
|
+
}
|
|
51
|
+
if (!archiveDir || typeof archiveDir !== 'string' || !archiveDir.trim()) {
|
|
52
|
+
return { ok: false, error: 'archive-domains requires --archive <milestone archive dir>' };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const root = path.resolve(projectDir || process.cwd());
|
|
56
|
+
const domainsRoot = path.resolve(root, DOMAINS_SUBPATH);
|
|
57
|
+
const archiveRootAbs = path.isAbsolute(archiveDir) ? archiveDir : path.resolve(root, archiveDir);
|
|
58
|
+
const archiveDomainsDir = path.join(archiveRootAbs, 'domains');
|
|
59
|
+
|
|
60
|
+
// Validate every target up front (fail-closed: no partial sweep on a bad name).
|
|
61
|
+
const plan = [];
|
|
62
|
+
for (const name of domains) {
|
|
63
|
+
if (!name || typeof name !== 'string' || name.includes('/') || name.includes('\\') || name === '.' || name === '..') {
|
|
64
|
+
return { ok: false, error: `invalid domain name (no path separators / dot-segments allowed): ${JSON.stringify(name)}` };
|
|
65
|
+
}
|
|
66
|
+
const srcAbs = path.resolve(domainsRoot, name);
|
|
67
|
+
// CONTAINMENT GUARD: must resolve strictly INSIDE .gsd-t/domains/ (not outside, not equal).
|
|
68
|
+
if (!(srcAbs.startsWith(domainsRoot + path.sep) && srcAbs !== domainsRoot)) {
|
|
69
|
+
return { ok: false, error: `containment violation: ${name} resolves to ${srcAbs}, outside or equal to ${domainsRoot} — refusing` };
|
|
70
|
+
}
|
|
71
|
+
plan.push({ name, srcAbs, destAbs: path.join(archiveDomainsDir, name) });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const archived = [];
|
|
75
|
+
const removed = [];
|
|
76
|
+
const skipped = [];
|
|
77
|
+
|
|
78
|
+
for (const { name, srcAbs, destAbs } of plan) {
|
|
79
|
+
const srcExists = fs.existsSync(srcAbs);
|
|
80
|
+
const destExists = fs.existsSync(destAbs);
|
|
81
|
+
|
|
82
|
+
// IDEMPOTENT: nothing live and already archived → skip silently.
|
|
83
|
+
if (!srcExists && destExists) { skipped.push(name); continue; }
|
|
84
|
+
// Nothing live and not archived → the domain doesn't exist at all → skip (not an error;
|
|
85
|
+
// re-running after a manual prune is valid).
|
|
86
|
+
if (!srcExists && !destExists) { skipped.push(name); continue; }
|
|
87
|
+
|
|
88
|
+
if (dryRun) {
|
|
89
|
+
if (!destExists) archived.push(name);
|
|
90
|
+
removed.push(name);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 1. Archive (copy) unless already archived.
|
|
95
|
+
if (!destExists) {
|
|
96
|
+
fs.mkdirSync(archiveDomainsDir, { recursive: true });
|
|
97
|
+
fs.cpSync(srcAbs, destAbs, { recursive: true });
|
|
98
|
+
archived.push(name);
|
|
99
|
+
}
|
|
100
|
+
// 2. Remove from live domains (containment re-checked at delete time).
|
|
101
|
+
if (!(srcAbs.startsWith(domainsRoot + path.sep) && srcAbs !== domainsRoot)) {
|
|
102
|
+
return { ok: false, error: `containment re-check failed at delete: ${srcAbs}` };
|
|
103
|
+
}
|
|
104
|
+
fs.rmSync(srcAbs, { recursive: true, force: true });
|
|
105
|
+
removed.push(name);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return { ok: true, archived, removed, skipped, dryRun };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { archiveDomains };
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// CLI
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
if (require.main === module) {
|
|
117
|
+
const argv = process.argv.slice(2);
|
|
118
|
+
const flags = {};
|
|
119
|
+
for (let i = 0; i < argv.length; i++) {
|
|
120
|
+
const a = argv[i];
|
|
121
|
+
if (a === '--dry-run') flags.dryRun = true;
|
|
122
|
+
else if (a === '--json') flags.json = true;
|
|
123
|
+
else if (a.startsWith('--') && i + 1 < argv.length) flags[a.slice(2)] = argv[++i];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const domains = (flags.domains || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
127
|
+
const result = archiveDomains({
|
|
128
|
+
domains,
|
|
129
|
+
archiveDir: flags.archive,
|
|
130
|
+
projectDir: flags.projectDir || process.cwd(),
|
|
131
|
+
dryRun: !!flags.dryRun,
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
process.stdout.write(JSON.stringify(result) + '\n');
|
|
135
|
+
process.exit(result.ok ? 0 : 1);
|
|
136
|
+
}
|
package/bin/gsd-t.js
CHANGED
|
@@ -1268,6 +1268,8 @@ const GLOBAL_BIN_TOOLS = [
|
|
|
1268
1268
|
"gsd-t-architectural-trigger.cjs",
|
|
1269
1269
|
// M90 D2 — Loop ledger (non-convergence detection + halt directive; §3).
|
|
1270
1270
|
"gsd-t-loop-ledger.cjs",
|
|
1271
|
+
// Backlog #40 — deterministic archive+sweep of a completed milestone's domain dirs.
|
|
1272
|
+
"gsd-t-archive-domains.cjs",
|
|
1271
1273
|
];
|
|
1272
1274
|
|
|
1273
1275
|
function installGlobalBinTools() {
|
|
@@ -2579,6 +2581,9 @@ const PROJECT_BIN_TOOLS = [
|
|
|
2579
2581
|
// M90 D2 — Loop ledger (non-convergence detection + halt directive; §3).
|
|
2580
2582
|
// Propagated so project-local runCli helpers (gsd-t-debug.workflow.js) can invoke it.
|
|
2581
2583
|
"gsd-t-loop-ledger.cjs",
|
|
2584
|
+
// Backlog #40 — deterministic archive+sweep of a completed milestone's domain dirs
|
|
2585
|
+
// (complete-milestone Step 7). Propagated so complete-milestone can invoke it project-local.
|
|
2586
|
+
"gsd-t-archive-domains.cjs",
|
|
2582
2587
|
];
|
|
2583
2588
|
|
|
2584
2589
|
// Files that older versions of this installer copied into project bin/ but
|
|
@@ -4754,6 +4759,20 @@ if (require.main === module) {
|
|
|
4754
4759
|
});
|
|
4755
4760
|
process.exit(res.status == null ? 1 : res.status);
|
|
4756
4761
|
}
|
|
4762
|
+
case "archive-domains": {
|
|
4763
|
+
// Backlog #40 — `gsd-t archive-domains --domains a,b --archive <dir>` deterministic
|
|
4764
|
+
// archive+sweep of a completed milestone's domain dirs (complete-milestone Step 7).
|
|
4765
|
+
const { spawnSync } = require("child_process");
|
|
4766
|
+
const js = path.join(__dirname, "gsd-t-archive-domains.cjs");
|
|
4767
|
+
if (!require("node:fs").existsSync(js)) {
|
|
4768
|
+
error(`gsd-t-archive-domains.cjs not found at ${js} — install or build backlog #40 first`);
|
|
4769
|
+
process.exit(1);
|
|
4770
|
+
}
|
|
4771
|
+
const res = spawnSync(process.execPath, [js, ...args.slice(1)], {
|
|
4772
|
+
stdio: "inherit",
|
|
4773
|
+
});
|
|
4774
|
+
process.exit(res.status == null ? 1 : res.status);
|
|
4775
|
+
}
|
|
4757
4776
|
case "metrics":
|
|
4758
4777
|
doMetrics(args.slice(1));
|
|
4759
4778
|
break;
|
|
@@ -401,8 +401,21 @@ node scripts/gsd-t-watch-state.js advance --agent-id "$GSD_T_AGENT_ID" --parent-
|
|
|
401
401
|
|
|
402
402
|
Reset `.gsd-t/` for next milestone:
|
|
403
403
|
|
|
404
|
-
1. Archive
|
|
405
|
-
|
|
404
|
+
1. **Archive + sweep the completed milestone's domains — DETERMINISTICALLY (backlog #40, NOT prose).**
|
|
405
|
+
This step was prose-only for ~30 milestones and got skipped, accumulating 77 stale domain dirs
|
|
406
|
+
that polluted the file-disjointness oracle. Run the helper with the EXPLICIT set of THIS
|
|
407
|
+
milestone's domain dirs (the partition's domain set — NOT a blanket wipe; a later still-active
|
|
408
|
+
milestone may legitimately have live domains):
|
|
409
|
+
```bash
|
|
410
|
+
gsd-t archive-domains --domains "{comma-separated domain dir names for THIS milestone}" \
|
|
411
|
+
--archive ".gsd-t/milestones/{name}-{date}" --projectDir .
|
|
412
|
+
```
|
|
413
|
+
(prefer the project-local `bin/gsd-t-archive-domains.cjs` when present). It (a) copies each named
|
|
414
|
+
domain → `<archive>/domains/<name>/`, then (b) removes it from `.gsd-t/domains/`. Idempotent
|
|
415
|
+
(re-running is a no-op), containment-guarded (refuses any name with path separators / dot-segments
|
|
416
|
+
or resolving outside `.gsd-t/domains/`), and fail-closed (a bad name aborts the whole batch — no
|
|
417
|
+
partial sweep). Domains belonging to other (still-active) milestones are left untouched.
|
|
418
|
+
2. (Step 1 already cleared this milestone's domains from `.gsd-t/domains/` — do NOT blanket-wipe the dir.)
|
|
406
419
|
3. Archive current reports → milestone folder
|
|
407
420
|
4. Clear `.gsd-t/impact-report.md`, `.gsd-t/test-coverage.md`
|
|
408
421
|
5. Update `.gsd-t/progress.md`:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.11",
|
|
4
4
|
"description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
|
|
5
5
|
"author": "Tekyz, Inc.",
|
|
6
6
|
"license": "MIT",
|