leerness 1.36.303 → 1.36.305

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 (188) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/CONTRIBUTING.md +2 -0
  3. package/README.md +4 -4
  4. package/bin/leerness.js +45 -19
  5. package/lib/claims-baseline.js +2 -1
  6. package/lib/io.js +6 -5
  7. package/lib/machine-cache.js +2 -1
  8. package/lib/memory-output.js +2 -1
  9. package/lib/migrate.js +2 -1
  10. package/lib/preview-serve.js +3 -2
  11. package/lib/runtime-writes.js +7 -1
  12. package/lib/safe-remove.js +132 -0
  13. package/lib/test-node-runner.js +2 -1
  14. package/lib/workspace-dir.js +3 -2
  15. package/package.json +2 -2
  16. package/scripts/agents-bench-output-probe.js +1 -0
  17. package/scripts/api-skill-match-probe.js +1 -0
  18. package/scripts/audit-env-text-probe.js +1 -0
  19. package/scripts/audit-readme-text-probe.js +1 -0
  20. package/scripts/audit-text-probe.js +1 -0
  21. package/scripts/authoring-language-probe.js +1 -0
  22. package/scripts/auto-update-hooks-probe.js +1 -0
  23. package/scripts/brief-text-probe.js +1 -0
  24. package/scripts/bugfix-areas-probe.js +1 -0
  25. package/scripts/ci-shard-probe.js +99 -0
  26. package/scripts/ci-shard.js +35 -3
  27. package/scripts/claim-runtime-cache-probe.js +1 -0
  28. package/scripts/claim-sweep-git-probe.js +1 -0
  29. package/scripts/claims-baseline-concurrency-probe.js +1 -0
  30. package/scripts/claims-baseline-probe.js +1 -0
  31. package/scripts/command-flags-probe.js +1 -0
  32. package/scripts/context-current-probe.js +1 -0
  33. package/scripts/context-reading-probe.js +1 -0
  34. package/scripts/copy-tree-probe.js +1 -0
  35. package/scripts/crud-check-probe.js +1 -0
  36. package/scripts/dead-flags-probe.js +1 -0
  37. package/scripts/done-status-probe.js +1 -0
  38. package/scripts/dry-run-refusal-probe.js +1 -0
  39. package/scripts/e2e-child-diagnostics-probe.js +1 -0
  40. package/scripts/e2e-command-surface.js +1 -0
  41. package/scripts/e2e-concurrency.js +1 -0
  42. package/scripts/e2e-core.js +1 -0
  43. package/scripts/e2e-lived-in-harness.js +1 -0
  44. package/scripts/e2e-repo-guard.js +1 -0
  45. package/scripts/e2e-temp-scope-probe.js +1 -0
  46. package/scripts/e2e.js +5 -0
  47. package/scripts/encoding-diagnostics-probe.js +1 -0
  48. package/scripts/encoding-selftest-probe.js +1 -0
  49. package/scripts/env-snapshot-text-probe.js +1 -0
  50. package/scripts/env-sync-text-probe.js +1 -0
  51. package/scripts/execution-ledger-sync-probe.js +1 -0
  52. package/scripts/false-claim-probe.js +1 -0
  53. package/scripts/feature-graph-text-probe.js +1 -0
  54. package/scripts/file-lease-probe.js +1 -0
  55. package/scripts/fix-evidence-language-probe.js +1 -0
  56. package/scripts/free-text-args-probe.js +1 -0
  57. package/scripts/generation-text-probe.js +1 -0
  58. package/scripts/git-resolution-cache-probe.js +1 -0
  59. package/scripts/github-login-probe.js +1 -0
  60. package/scripts/handoff-history-text-probe.js +1 -0
  61. package/scripts/handoff-i18n-probe.js +1 -0
  62. package/scripts/handoff-preservation-probe.js +1 -0
  63. package/scripts/handoff-readonly-probe.js +1 -0
  64. package/scripts/handoff-text-probe.js +1 -0
  65. package/scripts/harness-text-probe.js +1 -0
  66. package/scripts/history-retention-probe.js +1 -0
  67. package/scripts/history-retention-write-probe.js +1 -0
  68. package/scripts/i18n-next-cluster-probe.js +1 -0
  69. package/scripts/i18n-priority-surface-probe.js +1 -0
  70. package/scripts/install-text-probe.js +1 -0
  71. package/scripts/installed-cleanroom-probe.js +1 -0
  72. package/scripts/installed-handoff-lifecycle-probe.js +1 -0
  73. package/scripts/lazy-auto-track-probe.js +1 -0
  74. package/scripts/legacy-runtime-negative-probe.js +1 -0
  75. package/scripts/lock-heartbeat-probe.js +1 -0
  76. package/scripts/lock-probe.js +1 -0
  77. package/scripts/managed-text-probe.js +1 -0
  78. package/scripts/mcp-presence-probe.js +1 -0
  79. package/scripts/memory-archive-check-probe.js +1 -0
  80. package/scripts/memory-archive-retention-probe.js +1 -0
  81. package/scripts/memory-arena-cleanup-probe.js +1 -0
  82. package/scripts/memory-checked-input-probe.js +1 -0
  83. package/scripts/memory-guard-order-probe.js +1 -0
  84. package/scripts/memory-input-preflight-probe.js +1 -0
  85. package/scripts/memory-input-unit-probe.js +1 -0
  86. package/scripts/memory-language-probe.js +1 -0
  87. package/scripts/memory-legacy-retention-probe.js +1 -0
  88. package/scripts/memory-migration-concurrency-probe.js +1 -0
  89. package/scripts/memory-migration-test-support.js +11 -28
  90. package/scripts/memory-migration-transport-probe.js +1 -0
  91. package/scripts/memory-output-parent-race-probe.js +1 -0
  92. package/scripts/memory-output-posix-probe.js +1 -0
  93. package/scripts/memory-output-publication-probe.js +1 -0
  94. package/scripts/memory-output-unit-probe.js +1 -0
  95. package/scripts/memory-projection-retention-probe.js +1 -0
  96. package/scripts/memory-recovery-external-probe.js +1 -0
  97. package/scripts/memory-recovery-receipt-probe.js +1 -0
  98. package/scripts/memory-restore-canonical-probe.js +1 -0
  99. package/scripts/memory-utf8-admission-probe.js +1 -0
  100. package/scripts/memory-write-budget-meter.js +1 -0
  101. package/scripts/migration-compat.js +1 -0
  102. package/scripts/mode-text-probe.js +1 -0
  103. package/scripts/mutation-integrity-probe.js +1 -0
  104. package/scripts/next-action-suggestion-probe.js +1 -0
  105. package/scripts/openai-response-retrieval-probe.js +1 -0
  106. package/scripts/openai-ui-review-dispatch-probe.js +1 -0
  107. package/scripts/openai-ui-review-observation-probe.js +1 -0
  108. package/scripts/openai-ui-review-receipt-probe.js +1 -0
  109. package/scripts/parent-detect-selftest-probe.js +1 -0
  110. package/scripts/plain-language-reporting-probe.js +1 -0
  111. package/scripts/plan-text-probe.js +1 -0
  112. package/scripts/platform-smoke.js +1 -0
  113. package/scripts/provider-capacity-probe.js +1 -0
  114. package/scripts/race-overlap-probe.js +1 -0
  115. package/scripts/release-bump-probe.js +1 -0
  116. package/scripts/release-runtime-probe.js +1 -0
  117. package/scripts/reminder-text-probe.js +1 -0
  118. package/scripts/review-checkpoint-transport-probe.js +1 -0
  119. package/scripts/review-control-probe.js +1 -0
  120. package/scripts/review-feedback-probe.js +1 -0
  121. package/scripts/review-request-labels-probe.js +1 -0
  122. package/scripts/review-request-signals-probe.js +1 -0
  123. package/scripts/review-snapshot-transport-probe.js +1 -0
  124. package/scripts/role-agent-schema-probe.js +1 -0
  125. package/scripts/role-fallback-probe.js +1 -0
  126. package/scripts/role-file-identity-probe.js +1 -0
  127. package/scripts/role-store-loader-probe.js +1 -0
  128. package/scripts/rule-applicability-probe.js +1 -0
  129. package/scripts/rule-text-probe.js +1 -0
  130. package/scripts/rule-verification-probe.js +1 -0
  131. package/scripts/runtime-admission-probe.js +1 -0
  132. package/scripts/runtime-cost-probe.js +1 -0
  133. package/scripts/runtime-descriptor-identity-probe.js +1 -0
  134. package/scripts/runtime-layout-probe.js +1 -0
  135. package/scripts/runtime-repl-probe.js +1 -0
  136. package/scripts/runtime-replacement-probe.js +1 -0
  137. package/scripts/runtime-write-probe.js +1 -0
  138. package/scripts/safe-remove-probe.js +573 -0
  139. package/scripts/secret-scan-scope-probe.js +1 -0
  140. package/scripts/selftest-cleanup-probe.js +1 -0
  141. package/scripts/session-close-text-probe.js +1 -0
  142. package/scripts/shared-state-lock-probe.js +1 -0
  143. package/scripts/skill-archive-probe.js +1 -0
  144. package/scripts/smoke.js +1 -0
  145. package/scripts/state-inspect-cli-probe.js +1 -0
  146. package/scripts/state-scopes-probe.js +1 -0
  147. package/scripts/store-diagnostic-identity-probe.js +1 -0
  148. package/scripts/store-diagnostic-probe.js +1 -0
  149. package/scripts/store-values-probe.js +1 -0
  150. package/scripts/task-sync-roundtrip-probe.js +1 -0
  151. package/scripts/teams-text-probe.js +1 -0
  152. package/scripts/temp-cleanup-probe.js +437 -1
  153. package/scripts/temp-cleanup.js +186 -2
  154. package/scripts/test-execution-plan-probe.js +1 -0
  155. package/scripts/test-firestore-probe.js +1 -0
  156. package/scripts/test-http-probe.js +1 -0
  157. package/scripts/test-node-runner-probe.js +9 -4
  158. package/scripts/test-npm-script-probe.js +1 -0
  159. package/scripts/test-npm-script-runner-probe.js +1 -0
  160. package/scripts/test-prerequisite-probe.js +1 -0
  161. package/scripts/test-suite-list-runner-probe.js +1 -0
  162. package/scripts/title-args-probe.js +1 -0
  163. package/scripts/tracker-write-language-probe.js +1 -0
  164. package/scripts/ui-boundary-browser-probe.js +1 -0
  165. package/scripts/ui-boundary-contracts-probe.js +1 -0
  166. package/scripts/ui-boundary-dashboard-probe.js +1 -0
  167. package/scripts/ui-copy-boundary-probe.js +1 -0
  168. package/scripts/ui-review-attempt-probe.js +1 -0
  169. package/scripts/ui-review-authority-floor-probe.js +1 -0
  170. package/scripts/ui-review-authority-host-probe.js +1 -0
  171. package/scripts/ui-review-authorization-probe.js +1 -0
  172. package/scripts/ui-review-ledger-probe.js +1 -0
  173. package/scripts/ui-review-observation-probe.js +1 -0
  174. package/scripts/ui-review-proof-use-probe.js +1 -0
  175. package/scripts/ui-review-publication-probe.js +1 -0
  176. package/scripts/ui-review-recovery-probe.js +1 -0
  177. package/scripts/ui-review-result-probe.js +1 -0
  178. package/scripts/ui-review-run-probe.js +1 -0
  179. package/scripts/ui-review-target-probe.js +1 -0
  180. package/scripts/ui-review-transport-probe.js +1 -0
  181. package/scripts/verify-code-cross-runtime-probe.js +1 -0
  182. package/scripts/which-shim-probe.js +1 -0
  183. package/scripts/workspace-dir-lock-order-probe.js +1 -0
  184. package/scripts/workspace-dir-migration-probe.js +1 -0
  185. package/scripts/workspace-migration-heartbeat-probe.js +1 -0
  186. package/scripts/workspace-selection-probe.js +1 -0
  187. package/scripts/write-path-language-probe.js +1 -0
  188. package/scripts/write-primitive-language-probe.js +1 -0
@@ -4,6 +4,7 @@
4
4
  // migrated. Keeping the policy in this domain module prevents another
5
5
  // read-one-directory/write-another split brain.
6
6
  const fs = require('fs');
7
+ const { removeSync } = require('./safe-remove');
7
8
  const path = require('path');
8
9
  const crypto = require('crypto');
9
10
 
@@ -290,7 +291,7 @@ function _safeRemoveCanonicalDir(root, target) {
290
291
  if (resolvedTarget !== allowed || path.dirname(resolvedTarget) !== resolvedRoot || resolvedRoot === path.parse(resolvedRoot).root) {
291
292
  throw new WorkspaceDirectoryError('E_WORKSPACE_DIR_UNSAFE_REMOVE', `unsafe canonical rollback target: ${resolvedTarget}`);
292
293
  }
293
- fs.rmSync(resolvedTarget, { recursive: true, force: true });
294
+ removeSync(resolvedTarget, { recursive: true, force: true });
294
295
  }
295
296
 
296
297
  function _rewriteLegacyReference(file) {
@@ -915,7 +916,7 @@ function _migrateLegacyWorkspace(root, opts = {}) {
915
916
  legacyRenamed = false;
916
917
  }
917
918
  for (const item of overwritten.reverse()) { _beat(); _copyFile(item.backup, item.dst); }
918
- for (const file of created.reverse()) { _beat(); if (fs.existsSync(file)) fs.rmSync(file, { force: true }); }
919
+ for (const file of created.reverse()) { _beat(); if (fs.existsSync(file)) removeSync(file, { force: true }); }
919
920
  _beat();
920
921
  if (!canonicalExisted && fs.existsSync(state.canonical.abs)) _safeRemoveCanonicalDir(state.root, state.canonical.abs);
921
922
  } catch (rollbackError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.303",
3
+ "version": "1.36.305",
4
4
  "description": "The AI-coding operations layer that makes \"done\" require evidence — persistent memory, evidence-gated completion checks, and clean handoffs for any AI agent (Claude Code, Codex, Cursor). State lives as plain files in your repo. CLI + MCP, 0 runtime dependencies.",
5
5
  "keywords": [
6
6
  "leerness",
@@ -143,7 +143,7 @@
143
143
  "test:ledger-sync": "node ./scripts/execution-ledger-sync-probe.js",
144
144
  "test:state-lock": "node ./scripts/shared-state-lock-probe.js",
145
145
  "test:lock-heartbeat": "node ./scripts/lock-heartbeat-probe.js",
146
- "test:temp-cleanup": "node ./scripts/temp-cleanup-probe.js && node ./scripts/copy-tree-probe.js",
146
+ "test:temp-cleanup": "node ./scripts/temp-cleanup-probe.js && node ./scripts/copy-tree-probe.js && node ./scripts/safe-remove-probe.js",
147
147
  "test:path-setup": "node ./scripts/path-setup-powershell-probe.js",
148
148
  "test:skill-archive": "node ./scripts/skill-archive-probe.js",
149
149
  "test:memory-arena-cleanup": "node ./scripts/memory-arena-cleanup-probe.js",
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert');
3
4
  const fs = require('fs');
4
5
  const path = require('path');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0319: saved API skills (.leerness/api-skills/*.md) are matched against the in-progress task by handoff,
3
4
  // handoff --json, health --json and session close --json. All four read row.title and row.notes, which progress
4
5
  // rows do not have, so nothing ever matched. They now share one task-text helper (request and next action), and
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // Real CLI regressions for the configuration consumed by Codex/Claude hooks.
5
6
  const fs = require('fs'), os = require('os'), path = require('path'), cp = require('child_process');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0330: past fixes at completion. `bugfix receipt --areas` declares the files or folders a fix is about;
3
4
  // verify-claim then lists the bugfix receipts whose declared areas hold a file the task's evidence names: finished
4
5
  // ones (their task is done, also in retained history) as past fixes with the root cause and the probe to re-run,
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
  // T-0359: CI runs e2e.js cases and npm-script leaves in parallel shards (scripts/ci-shard.js). A shard that drops a
4
5
  // unit fails nothing — the unit silently stops running — so this probe checks the parts that decide what runs:
5
6
  // the assignment is a deterministic partition, every leaf of the real sharded scripts parses and lands in exactly
@@ -151,6 +152,104 @@ try {
151
152
  assert.strictEqual(env.LEERNESS_CI_SHARD_TIMES, times, 'the caller\'s environment object was changed');
152
153
  });
153
154
 
155
+ check('runNpm removes the temp folders a passing leaf and its tracked child recorded, and keeps a failing or killed leaf\'s (T-0357)', () => {
156
+ // The leaf only records (scripts/temp-cleanup.js trackTemp); the runner, which sees the exit status, removes.
157
+ const dir = path.join(arena, 'temp-record');
158
+ const leafTemp = path.join(dir, 'temp');
159
+ fs.mkdirSync(leafTemp, { recursive: true });
160
+ const TC = JSON.stringify(path.join(__dirname, 'temp-cleanup.js'));
161
+ const make = (name, end) => fs.writeFileSync(path.join(dir, `${name}.js`), [
162
+ `require(${TC}).trackTemp(module);`,
163
+ "const fs = require('fs'), os = require('os'), path = require('path'), cp = require('child_process');",
164
+ `const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-leaf-${name}-'));`,
165
+ `const c = cp.spawnSync(process.execPath, [${JSON.stringify(path.join(dir, 'child.js'))}], { encoding: 'utf8' }).stdout.trim();`,
166
+ `fs.writeFileSync(${JSON.stringify(path.join(dir, `${name}.out`))}, JSON.stringify({ d, c, rec: process.env.LEERNESS_TEMP_RECORD || null }));`,
167
+ end, ''].join('\n'));
168
+ fs.writeFileSync(path.join(dir, 'child.js'), [`require(${TC}).trackTemp(module);`,
169
+ "const fs = require('fs'), os = require('os'), path = require('path');",
170
+ "process.stdout.write(fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-leaf-child-')));", ''].join('\n'));
171
+ make('pass', 'process.exitCode = 0;');
172
+ make('fail', 'process.exitCode = 1;');
173
+ make('killed', "process.kill(process.pid, 'SIGKILL');");
174
+ const recordRoots = () => fs.readdirSync(os.tmpdir()).filter(n => n.startsWith('leerness-temp-record-')).sort();
175
+ const rootsBefore = recordRoots();
176
+ const lines = [];
177
+ const env = { ...process.env, GITHUB_ACTIONS: '', TEMP: leafTemp, TMP: leafTemp, TMPDIR: leafTemp, LEERNESS_TEMP_RECORD: path.join(dir, 'outer-record') };
178
+ const r = cs.runNpm('suite', { index: 1, count: 1 }, { root: dir, scripts: { suite: 'node ./pass.js && node ./fail.js && node ./killed.js' }, weights: { e2e: {}, npm: {} }, stdio: 'ignore', env, log: l => lines.push(l) });
179
+ assert.deepStrictEqual(r.failed, ['node ./fail.js', 'node ./killed.js']);
180
+ const res = n => JSON.parse(fs.readFileSync(path.join(dir, `${n}.out`), 'utf8'));
181
+ const pass = res('pass'), fail = res('fail');
182
+ assert.ok(pass.d && pass.c && fail.d && fail.c, JSON.stringify({ pass, fail }));
183
+ assert.strictEqual(fs.existsSync(pass.d), false, 'the passing leaf\'s folder was kept');
184
+ assert.strictEqual(fs.existsSync(pass.c), false, 'the passing leaf\'s tracked child\'s folder was kept');
185
+ assert.strictEqual(fs.existsSync(fail.d), true, 'the failing leaf\'s folder was removed');
186
+ assert.strictEqual(fs.existsSync(fail.c), true, 'the failing leaf\'s child\'s folder was removed');
187
+ const killedMade = fs.readdirSync(leafTemp).filter(n => n.startsWith('leerness-leaf-killed-'));
188
+ assert.strictEqual(killedMade.length, 1, 'the killed leaf\'s folder was removed');
189
+ assert.ok(pass.rec && fail.rec && pass.rec !== fail.rec && pass.rec !== env.LEERNESS_TEMP_RECORD, 'each leaf gets its own record directory from the runner');
190
+ assert.strictEqual(fs.existsSync(pass.rec) || fs.existsSync(fail.rec), false, 'a record directory was left');
191
+ assert.deepStrictEqual(recordRoots(), rootsBefore, 'the runner left its record root');
192
+ assert.ok(lines.some(l => /^ci-shard: temp folders: 2 removed after passing leaves/.test(l)), lines.join('\n'));
193
+ assert.strictEqual(env.LEERNESS_TEMP_RECORD, path.join(dir, 'outer-record'), 'the caller\'s environment object was changed');
194
+ });
195
+
196
+ // 1.36.304 review round 3: the record root was removed recursively without checking that it was still the folder the
197
+ // runner made. A wrapper of fs.mkdtempSync (or fs.mkdirSync, for a leaf's directory) installed before the runner
198
+ // can put an existing folder there; the runner now checks each directory right after making it (its own identity,
199
+ // empty), records nothing into one that fails, removes only the leaf directories it checked, and the root only when
200
+ // it is empty.
201
+ check('runNpm removes no folder that a wrapper swapped in for its record root or a leaf\'s record directory (T-0357)', () => {
202
+ const dir = path.join(arena, 'temp-record-swap');
203
+ const leafTemp = path.join(dir, 'temp');
204
+ fs.mkdirSync(leafTemp, { recursive: true });
205
+ fs.writeFileSync(path.join(dir, 'pass.js'), [`require(${JSON.stringify(path.join(__dirname, 'temp-cleanup.js'))}).trackTemp(module);`,
206
+ "const fs = require('fs'), os = require('os'), path = require('path');",
207
+ "fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-leaf-swap-'));", ''].join('\n'));
208
+ for (const target of ['root', 'leaf']) {
209
+ const unrelated = path.join(dir, `unrelated-${target}`);
210
+ fs.mkdirSync(unrelated); fs.writeFileSync(path.join(unrelated, 'keep.txt'), 'keep');
211
+ const saved = { mkdtempSync: fs.mkdtempSync, mkdirSync: fs.mkdirSync };
212
+ let swappedAt = null;
213
+ const swapIn = made => { fs.renameSync(made, made + '-aside'); fs.renameSync(unrelated, made); swappedAt = made; };
214
+ if (target === 'root') fs.mkdtempSync = function (prefix, ...rest) { const d = saved.mkdtempSync.call(fs, prefix, ...rest); if (String(prefix).includes('leerness-temp-record-')) swapIn(d); return d; };
215
+ else fs.mkdirSync = function (p, ...rest) { const r = saved.mkdirSync.call(fs, p, ...rest); if (!swappedAt && /leerness-temp-record-/.test(path.dirname(String(p)))) swapIn(String(p)); return r; };
216
+ let r;
217
+ const lines = [];
218
+ try {
219
+ r = cs.runNpm('suite', { index: 1, count: 1 }, { root: dir, scripts: { suite: 'node ./pass.js' }, weights: { e2e: {}, npm: {} }, stdio: 'ignore',
220
+ env: { ...process.env, GITHUB_ACTIONS: '', TEMP: leafTemp, TMP: leafTemp, TMPDIR: leafTemp }, log: l => lines.push(l) });
221
+ } finally { fs.mkdtempSync = saved.mkdtempSync; fs.mkdirSync = saved.mkdirSync; }
222
+ assert.deepStrictEqual(r.failed, [], target);
223
+ assert.ok(swappedAt, `${target}: the wrapper did not run`);
224
+ assert.strictEqual(fs.readFileSync(path.join(swappedAt, 'keep.txt'), 'utf8'), 'keep', `${target}: the folder swapped in was removed`);
225
+ assert.ok(lines.some(l => /^ci-shard: temp folders: .*1 leaves not recorded/.test(l)), `${target}: the leaf was given a directory that failed the check\n${lines.join('\n')}`);
226
+ // What the runner rightly leaves is this test's own, in the temp directory: for the root, the folder swapped in and
227
+ // the root moved aside; for a leaf's directory, the root that holds them (1.36.304 full run: they were left).
228
+ const own = target === 'root' ? [swappedAt, swappedAt + '-aside'] : [path.dirname(swappedAt)];
229
+ for (const p of own) {
230
+ assert.ok(path.resolve(path.dirname(p)) === path.resolve(os.tmpdir()) && path.basename(p).startsWith('leerness-temp-record-'), `${target}: not the runner's folder: ${p}`);
231
+ fs.rmSync(p, { recursive: true, force: true });
232
+ }
233
+ }
234
+ });
235
+
236
+ check('runNpm removes its record root only when it is empty: a folder a leaf moved into it is left (T-0357)', () => {
237
+ const dir = path.join(arena, 'temp-record-into-root');
238
+ const leafTemp = path.join(dir, 'temp');
239
+ fs.mkdirSync(leafTemp, { recursive: true });
240
+ const moved = path.join(dir, 'moved');
241
+ fs.mkdirSync(moved); fs.writeFileSync(path.join(moved, 'keep.txt'), 'keep');
242
+ fs.writeFileSync(path.join(dir, 'mover.js'), ["const fs = require('fs'), path = require('path');",
243
+ `const into = path.join(path.dirname(process.env.LEERNESS_TEMP_RECORD), 'moved'); fs.renameSync(${JSON.stringify(moved)}, into);`,
244
+ `fs.writeFileSync(${JSON.stringify(path.join(dir, 'into.txt'))}, into);`, ''].join('\n'));
245
+ const r = cs.runNpm('suite', { index: 1, count: 1 }, { root: dir, scripts: { suite: 'node ./mover.js' }, weights: { e2e: {}, npm: {} }, stdio: 'ignore',
246
+ env: { ...process.env, GITHUB_ACTIONS: '', TEMP: leafTemp, TMP: leafTemp, TMPDIR: leafTemp }, log: () => {} });
247
+ assert.deepStrictEqual(r.failed, []);
248
+ const into = fs.readFileSync(path.join(dir, 'into.txt'), 'utf8');
249
+ assert.strictEqual(fs.readFileSync(path.join(into, 'keep.txt'), 'utf8'), 'keep', 'the folder moved into the record root was removed with it');
250
+ fs.rmSync(path.dirname(into), { recursive: true, force: true });
251
+ });
252
+
154
253
  const CREATE = names => `const e2eShard = require('./ci-shard').e2eShard(__filename, [${names.map(n => `'${n}'`).join(', ')}]);`;
155
254
  const START = '// e2e-shard: cases start here';
156
255
 
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
  // T-0359: split CI work across parallel jobs. Until 1.36.300 each Windows gate job ran e2e.js (167-226 min) and the
4
5
  // command-surface leaves (43-52 min) one after the other, and the Windows fast job ran its leaves for 79 min, so a
5
6
  // release waited about five hours for CI (run 38193803239). A shard runs part of that work. Shards 1..k of one job
@@ -17,11 +18,17 @@
17
18
  // guessed: running it differently from npm would test something else. Leaves run with node directly, in the
18
19
  // package root, in their order in the script. A shard runs all of its leaves and fails if any failed, so one CI
19
20
  // run reports every failing leaf of the shard (a plain `&&` chain stops at the first).
21
+ // T-0357: each leaf records the temp folders it makes (scripts/temp-cleanup.js trackTemp, into a directory this runner
22
+ // gives it through LEERNESS_TEMP_RECORD), and the runner removes them after the leaf ended with status 0, no signal
23
+ // and no spawn error: the verdict it uses for pass/fail, read from the exit status, which no code in the leaf can
24
+ // change afterwards. A failed leaf's folders are kept for inspection.
20
25
  // e2e.js uses e2eShard() below: each top-level case is `if (e2eShard.take('<key>')) {`, and finish() checks that the
21
26
  // file has no unguarded top-level block and that every guard was reached exactly once.
22
27
  const fs = require('fs');
28
+ const os = require('os');
23
29
  const path = require('path');
24
30
  const cp = require('child_process');
31
+ const temp = require('./temp-cleanup');
25
32
 
26
33
  const ROOT = path.resolve(__dirname, '..');
27
34
  const WEIGHTS_FILE = path.join(__dirname, 'ci-shard-weights.json');
@@ -139,23 +146,48 @@ function runNpm(name, spec, opts = {}) {
139
146
  const grouped = env.GITHUB_ACTIONS === 'true';
140
147
  // Leaves do not see this runner's times file: a leaf that runs runNpm itself (ci-shard-probe.js) would overwrite it.
141
148
  const leafEnv = { ...env };
142
- for (const k of Object.keys(leafEnv)) if (k.toUpperCase() === 'LEERNESS_CI_SHARD_TIMES') delete leafEnv[k];
149
+ for (const k of Object.keys(leafEnv)) if (['LEERNESS_CI_SHARD_TIMES', temp.RECORD_ENV].includes(k.toUpperCase())) delete leafEnv[k];
143
150
  log(`ci-shard: ${name} shard ${spec.index}/${spec.count} runs ${mine.length} of ${leaves.length} leaves`);
151
+ // T-0357: one record directory per leaf, below a root this runner makes. Each is checked right after it is made (an
152
+ // empty directory, not a link, its identity kept): a wrapper of fs.mkdtempSync or fs.mkdirSync installed before
153
+ // the runner could put an existing folder there (1.36.304 review round 3). A leaf whose directory fails the check
154
+ // gets no variable and records nothing; only checked directories are removed, and the root only when it is empty.
155
+ const freshDir = d => {
156
+ try {
157
+ const st = fs.lstatSync(d, { bigint: true });
158
+ return st.isSymbolicLink() || !st.isDirectory() || fs.readdirSync(d).length ? null : temp.ownTree(d);
159
+ } catch { return null; }
160
+ };
161
+ let recordRoot = null;
162
+ try { recordRoot = freshDir(fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-temp-record-'))); } catch { /* nothing recorded */ }
163
+ const tempCount = { removed: 0, left: 0, unrecorded: 0 };
144
164
  const results = [];
145
- for (const leaf of mine) {
165
+ for (const [i, leaf] of mine.entries()) {
146
166
  if (grouped) log(`::group::${leaf.key}`);
167
+ let recordDir = null;
168
+ if (recordRoot) { const d = path.join(recordRoot.dir, String(i)); try { fs.mkdirSync(d); recordDir = freshDir(d); } catch { /* nothing recorded */ } }
169
+ if (!recordDir) tempCount.unrecorded++;
147
170
  const started = Date.now();
148
- const r = cp.spawnSync(process.execPath, [path.resolve(root, leaf.file), ...leaf.args], { cwd: root, stdio: opts.stdio || 'inherit', env: leafEnv });
171
+ const r = cp.spawnSync(process.execPath, [path.resolve(root, leaf.file), ...leaf.args], { cwd: root, stdio: opts.stdio || 'inherit', env: recordDir ? { ...leafEnv, [temp.RECORD_ENV]: recordDir.dir } : leafEnv });
149
172
  const seconds = (Date.now() - started) / 1000;
150
173
  const ok = r.status === 0 && !r.signal && !r.error;
174
+ if (ok && recordDir) { const t = temp.removeRecorded(recordDir.dir); tempCount.removed += t.removed; tempCount.left += t.left; }
175
+ if (recordDir) try { temp.removeOwnedTree(recordDir, { timeoutMs: 2000 }); } catch { /* left behind */ }
151
176
  results.push({ key: leaf.key, ok, seconds, status: r.status, signal: r.signal || null, error: r.error ? String(r.error.code || r.error.message) : null });
152
177
  if (grouped) log('::endgroup::');
153
178
  log(`ci-shard: ${ok ? 'ok ' : 'FAIL'} ${seconds.toFixed(1).padStart(7)} s ${leaf.key}${ok ? '' : ` (exit ${r.status}${r.signal ? `, signal ${r.signal}` : ''}${r.error ? `, ${r.error.code || r.error.message}` : ''})`}`);
154
179
  }
180
+ if (recordRoot) {
181
+ try {
182
+ const st = fs.lstatSync(recordRoot.dir, { bigint: true });
183
+ if (!st.isSymbolicLink() && st.dev === recordRoot.dev && st.ino === recordRoot.ino) fs.rmdirSync(recordRoot.dir);
184
+ } catch { /* not empty or replaced: left behind */ }
185
+ }
155
186
  const failed = results.filter(r => !r.ok);
156
187
  const total = results.reduce((s, r) => s + r.seconds, 0);
157
188
  log(`ci-shard: ${name} shard ${spec.index}/${spec.count}: ${results.length - failed.length}/${results.length} leaves passed · ${total.toFixed(0)} s`);
158
189
  for (const f of failed) log(`ci-shard: failed: ${f.key}`);
190
+ log(`ci-shard: temp folders: ${tempCount.removed} removed after passing leaves${tempCount.left ? `, ${tempCount.left} left (replaced, outside the temp directory, or still held)` : ''}${tempCount.unrecorded ? `, ${tempCount.unrecorded} leaves not recorded (their record directory was not the one made)` : ''}; a failed leaf keeps its own`);
159
191
  if (env.LEERNESS_CI_SHARD_TIMES) {
160
192
  fs.writeFileSync(env.LEERNESS_CI_SHARD_TIMES, JSON.stringify(Object.fromEntries(results.map(r => [r.key, Math.round(r.seconds * 10) / 10])), null, 1) + '\n');
161
193
  }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0303: generated runtime cache must not create a strict Git mismatch for
5
6
  // an unrelated, already committed claim when an older project lacks .gitignore.
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0302: count real Git status requests at the CLI boundary, rather than
5
6
  // matching the implementation's source text.
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0145 multi-session regression: two first-time creators may both observe
5
6
  // "absent", but the create-only check inside the file lock must let exactly
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0145 regression probe: a reviewed legacy debt boundary unblocks only exact
5
6
  // historical failures. Raw/per-task audits, changed rows, new failures, invalid
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  const fs = require('fs');
5
6
  const crypto = require('crypto');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { removeTree } = require('./temp-cleanup');
4
5
  if (!process.argv.includes('--worker')) {
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0343: lib/copy-tree.js copyTreeSync, the fs.cpSync replacement used for fixture and selftest copies.
3
4
  // It must copy nested directories and file bytes exactly, copy into an existing directory, never overwrite a file,
4
5
  // and work with non-ASCII paths on every supported Node (fs.cpSync crashes on them in Node 22.23.3 on Windows).
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0323 (owner request 2026-09-30): data features get the CRUD operations they need, by default and in every
3
4
  // leerness project. The owner chose "guidance plus an automatic check at completion" (advisory, never blocking),
4
5
  // for new features and changes to existing ones, and "the four operations plus quality items" as the standard:
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0094: advertised options must change observable behavior (or fail closed).
5
6
  // This probe intentionally uses isolated projects and real CLI processes so a
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0332: which tracker statuses read as done. verify-claim (one task, and --all, which gate --claims runs), the claims
3
4
  // baseline, lazy detection and past-fix advice share one exact predicate: done, completed, verified or 완료, in any
4
5
  // case, with the cell's spaces trimmed, also inside one pair of brackets (the older tracker form, like [진행]).
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0339: commands whose edits need a state lock refuse --dry-run with the dry-run refusal. Under --dry-run the CLI
3
4
  // takes no state lock (it would leave lock metadata), so lib/managed-text.js refused every edit with "Managed text
4
5
  // requires its existing state lock" (E_TEXT_LOCK) before its first write could meet the dry-run guard (task add, drop
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
 
3
4
  // Observation integration only: original E2E predicates, real temp files, no heavyweight CLI.
4
5
  const assert = require('node:assert/strict');
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0099 / UR-0076: historically uncovered command families.
5
6
  // Local state commands execute end-to-end in a disposable fixture. Commands that
@@ -1,6 +1,7 @@
1
1
  // scripts/e2e-concurrency.js — T-0115의 silent lost-update 회귀 방지.
2
2
  // LOCKPROBE_STALL_*은 scripts/lock-probe.js를 명시 preload한 이 테스트에서만 활성화된다.
3
3
  'use strict';
4
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
4
5
 
5
6
  const fs = require('fs');
6
7
  const os = require('os');
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // 1.35.13 (UR-0014): 3-tier 테스트의 중간 티어 — critical-path 행위(behavioral) 스위트.
5
6
  // test:fast (smoke.js) — 명령이 크래시 없이 도는가만 clean 프로젝트에서 확인, <1분. 개발 루프.
@@ -10,6 +10,7 @@
10
10
  // dated decision, a lesson, an active rule, an open user request, a closed session (a handoff naming the
11
11
  // in-progress task, the current state, a pre-wake report), the manifest, the installed skills and the usage counter.
12
12
  'use strict';
13
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
13
14
  const fs = require('fs');
14
15
  const path = require('path');
15
16
  const cp = require('child_process');
@@ -8,6 +8,7 @@
8
8
  // A snapshot records size, mtime (ns) and inode of every file and each directory, gitignored state included.
9
9
  // Content hashes are unnecessary: a write moves mtime, and a rename or hard link changes the inode.
10
10
  'use strict';
11
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
11
12
  const fs = require('fs');
12
13
  const os = require('os');
13
14
  const path = require('path');
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0182: execute the original E2E counter and failure assertion, with real
5
6
  // process-local TMP isolation. No product implementation or fs/os mock.
package/scripts/e2e.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  const fs = require('fs');
5
6
  const os = require('os');
@@ -15957,6 +15958,10 @@ setImmediate(() => {
15957
15958
  failed++;
15958
15959
  console.log(`✗ T-0359 e2e 샤드 가드 이상 ${JSON.stringify({ problems: _shard.problems.slice(0, 10), missing: _shard.missing.slice(0, 10), repeated: _shard.repeated.slice(0, 10), unknown: _shard.unknown.slice(0, 10) })}`);
15959
15960
  }
15961
+ // T-0357: 케이스가 만든 임시 폴더는 이 프로세스가 기록만 하고, 이 실행을 시작한 러너(scripts/ci-shard.js)가 종료 코드 0 을
15962
+ // 보고 지운다(링크는 링크로만). 러너 없이 실행하면 기록하지 않는다.
15963
+ const _temp = require('./temp-cleanup').trackTemp(module);
15964
+ if (_temp.active) console.log(` 임시 폴더 ${_temp.pending()}개 기록 — 러너가 종료 코드 0 을 보면 삭제, 실패면 남김`);
15960
15965
  console.log(`\nE2E result: ${total - failed}/${total} passed${_shard.spec ? ` · shard ${_shard.spec.index}/${_shard.spec.count} (${_shard.ran}/${_shard.total} cases)` : ''} · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
15961
15966
  if (failed > 0) process.exitCode = 1;
15962
15967
  });
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { LEGACY_WORKSPACE_DIR } = require('../lib/workspace-dir');
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0181: test the confirmed UR-0113 diagnostic/exit-code defect separately
5
6
  // from the unconfirmed Windows CI failure. The default violates the platform's
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { removeTree } = require('./temp-cleanup');
4
5
  if (!process.argv.includes('--worker')) {
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
 
3
4
  // Real filesystem writes and process termination; injected I/O faults are
4
5
  // explicitly synthetic. These checks do not simulate physical power loss.
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0095 regression probe: read/status commands must state what they actually
5
6
  // measured. A CLI version is not a project version, an invalid memory verb is
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { removeTree } = require('./temp-cleanup');
4
5
  if (!process.argv.includes('--worker')) {
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // Exact-file leases are an opt-in coordination primitive. This probe deliberately
5
6
  // distinguishes them from the discarded ambient scope advisory: only explicit calls
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0333: `task fix-evidence` speaks the project's language. It printed Korean everywhere: in an English project the
3
4
  // header, the guidance, the per-row lines, the suggested evidence and the results of `--set` and of an empty list were
4
5
  // Korean. English projects now get English; Korean output stays byte for byte (every expected line below is the text
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0321: free text given to a command is exactly the positional text the user typed.
3
4
  // Reported: `clarify "<request>" --path <dir>` appends <dir> to the request. That did not reproduce —
4
5
  // main() builds args with nonFlagArgs(), which already drops registered value flags and their values; the
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  const assert = require('assert');
5
6
  const fs = require('fs');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
 
3
4
  // Real loopback TLS; only the test intercepts DNS destination/trust roots.
4
5
  const assert = require('node:assert/strict');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  const assert = require('assert'), fs = require('fs'), path = require('path'), os = require('os'), cp = require('child_process');
3
4
  const { createHash } = require('crypto');
4
5
  const { removeTree } = require('./temp-cleanup');
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
2
3
  // T-0317: English handoff output carries no Korean-only tool text. An English project on Korean Windows saw the
3
4
  // terminal-encoding notice, skill suggestions, the official-skill cache note, next-step suggestions and the compact
4
5
  // "다음:" line in Korean. Every line the tool writes on the handoff paths now goes through a language helper whose
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
+ require('./temp-cleanup').trackTemp(module); // T-0357: records this run's temp folders; the runner that started it removes them after a clean exit
3
4
 
4
5
  // T-0307: session close keeps user-written handoff text by position, and every reader of the
5
6
  // handoff looks at the current generated region rather than the whole file.