@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 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.10** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
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 current domains `.gsd-t/milestones/{name}/domains/`
405
- 2. Clear `.gsd-t/domains/` (empty, ready for next partition)
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.10",
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",