@hegemonart/get-design-done 1.31.0 → 1.31.5

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 (163) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +44 -0
  4. package/NOTICE +224 -0
  5. package/README.md +1 -1
  6. package/agents/design-authority-watcher.md +1 -1
  7. package/agents/perf-analyzer.md +2 -2
  8. package/bin/gdd-mcp +78 -0
  9. package/bin/gdd-sdk +34 -24
  10. package/bin/gdd-state-mcp +78 -0
  11. package/{README.de.md → docs/i18n/README.de.md} +1 -1
  12. package/{README.fr.md → docs/i18n/README.fr.md} +1 -1
  13. package/{README.it.md → docs/i18n/README.it.md} +1 -1
  14. package/{README.ja.md → docs/i18n/README.ja.md} +1 -1
  15. package/{README.ko.md → docs/i18n/README.ko.md} +1 -1
  16. package/{README.zh-CN.md → docs/i18n/README.zh-CN.md} +1 -1
  17. package/hooks/_hook-emit.js +1 -1
  18. package/hooks/budget-enforcer.ts +5 -5
  19. package/hooks/context-exhaustion.ts +2 -2
  20. package/hooks/gdd-precompact-snapshot.js +3 -3
  21. package/hooks/gdd-read-injection-scanner.ts +2 -2
  22. package/hooks/gdd-sessionstart-recap.js +1 -1
  23. package/hooks/gdd-turn-closeout.js +1 -1
  24. package/package.json +20 -9
  25. package/recipes/.gitkeep +0 -0
  26. package/reference/schemas/recipe.schema.json +33 -0
  27. package/scripts/cli/gdd-events.mjs +5 -5
  28. package/scripts/lib/cache/gdd-cache-manager.cjs +1 -1
  29. package/scripts/lib/cli/index.ts +22 -160
  30. package/scripts/lib/connection-probe/index.cjs +1 -1
  31. package/scripts/lib/discuss-parallel-runner/aggregator.ts +1 -1
  32. package/scripts/lib/discuss-parallel-runner/index.ts +1 -1
  33. package/scripts/lib/error-classifier.cjs +24 -227
  34. package/scripts/lib/event-stream/index.ts +25 -193
  35. package/scripts/lib/gdd-errors/index.ts +24 -213
  36. package/scripts/lib/gdd-state/index.ts +23 -161
  37. package/scripts/lib/iteration-budget.cjs +23 -199
  38. package/scripts/lib/jittered-backoff.cjs +24 -107
  39. package/scripts/lib/lockfile.cjs +23 -195
  40. package/scripts/lib/logger/index.ts +1 -1
  41. package/scripts/lib/parallelism-engine/concurrency-tuner.cjs +1 -1
  42. package/scripts/lib/perf-analyzer/index.cjs +1 -1
  43. package/scripts/lib/pipeline-runner/index.ts +4 -4
  44. package/scripts/lib/pipeline-runner/state-machine.ts +1 -1
  45. package/scripts/lib/prompt-dedup/index.cjs +1 -1
  46. package/scripts/lib/rate-guard.cjs +2 -2
  47. package/scripts/lib/recipe-loader.cjs +142 -0
  48. package/scripts/lib/session-runner/errors.ts +3 -3
  49. package/scripts/lib/session-runner/index.ts +3 -3
  50. package/scripts/lib/session-runner/transcript.ts +1 -1
  51. package/scripts/lib/tool-scoping/index.ts +1 -1
  52. package/scripts/mcp-servers/gdd-mcp/server.ts +29 -311
  53. package/scripts/mcp-servers/gdd-state/server.ts +28 -282
  54. package/sdk/README.md +45 -0
  55. package/{scripts/lib → sdk}/cli/commands/audit.ts +3 -3
  56. package/{scripts/lib → sdk}/cli/commands/init.ts +3 -3
  57. package/{scripts/lib → sdk}/cli/commands/query.ts +4 -4
  58. package/{scripts/lib → sdk}/cli/commands/run.ts +5 -5
  59. package/{scripts/lib → sdk}/cli/commands/stage.ts +5 -5
  60. package/sdk/cli/index.js +8091 -0
  61. package/sdk/cli/index.ts +172 -0
  62. package/{scripts/lib → sdk}/cli/parse-args.ts +2 -2
  63. package/{scripts/lib/gdd-errors → sdk/errors}/classification.ts +1 -1
  64. package/sdk/errors/index.ts +218 -0
  65. package/{scripts/lib → sdk}/event-stream/emitter.ts +1 -1
  66. package/sdk/event-stream/index.ts +197 -0
  67. package/{scripts/lib → sdk}/event-stream/reader.ts +1 -1
  68. package/{scripts/lib → sdk}/event-stream/types.ts +2 -2
  69. package/{scripts/lib → sdk}/event-stream/writer.ts +1 -1
  70. package/sdk/index.ts +19 -0
  71. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/README.md +3 -3
  72. package/sdk/mcp/gdd-mcp/server.js +1924 -0
  73. package/sdk/mcp/gdd-mcp/server.ts +325 -0
  74. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_cycle_recap.ts +3 -3
  75. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_decisions_list.ts +2 -2
  76. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_events_tail.ts +3 -3
  77. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_health.ts +2 -2
  78. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_intel_get.ts +2 -2
  79. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_learnings_digest.ts +2 -2
  80. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phase_current.ts +2 -2
  81. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phases_list.ts +2 -2
  82. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_plans_list.ts +2 -2
  83. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_reflections_latest.ts +2 -2
  84. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_status.ts +3 -3
  85. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_telemetry_query.ts +3 -3
  86. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/index.ts +2 -2
  87. package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/shared.ts +3 -3
  88. package/sdk/mcp/gdd-state/server.js +2790 -0
  89. package/sdk/mcp/gdd-state/server.ts +294 -0
  90. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_blocker.ts +3 -3
  91. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_decision.ts +3 -3
  92. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_must_have.ts +3 -3
  93. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/checkpoint.ts +2 -2
  94. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/frontmatter_update.ts +2 -2
  95. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/get.ts +3 -3
  96. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/index.ts +1 -1
  97. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/probe_connections.ts +3 -3
  98. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/resolve_blocker.ts +3 -3
  99. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/set_status.ts +2 -2
  100. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/shared.ts +8 -8
  101. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/transition_stage.ts +4 -4
  102. package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/update_progress.ts +2 -2
  103. package/sdk/primitives/error-classifier.cjs +232 -0
  104. package/sdk/primitives/iteration-budget.cjs +205 -0
  105. package/sdk/primitives/jittered-backoff.cjs +112 -0
  106. package/sdk/primitives/lockfile.cjs +201 -0
  107. package/{scripts/lib/gdd-state → sdk/state}/gates.ts +1 -1
  108. package/sdk/state/index.ts +167 -0
  109. package/{scripts/lib/gdd-state → sdk/state}/lockfile.ts +1 -1
  110. package/{scripts/lib/gdd-state → sdk/state}/mutator.ts +1 -1
  111. package/{scripts/lib/gdd-state → sdk/state}/parser.ts +1 -1
  112. package/{scripts/lib/gdd-state → sdk/state}/types.ts +4 -4
  113. package/skills/quality-gate/SKILL.md +2 -2
  114. package/scripts/aggregate-agent-metrics.ts +0 -282
  115. package/scripts/bootstrap-manifest.txt +0 -3
  116. package/scripts/bootstrap.sh +0 -80
  117. package/scripts/build-distribution-bundles.cjs +0 -549
  118. package/scripts/build-intel.cjs +0 -486
  119. package/scripts/codegen-schema-types.ts +0 -149
  120. package/scripts/detect-stale-refs.cjs +0 -107
  121. package/scripts/e2e/run-headless.ts +0 -514
  122. package/scripts/extract-changelog-section.cjs +0 -58
  123. package/scripts/gsd-cleanup-incubator.cjs +0 -367
  124. package/scripts/injection-patterns.cjs +0 -58
  125. package/scripts/lint-agentskills-spec.cjs +0 -457
  126. package/scripts/release-smoke-test.cjs +0 -200
  127. package/scripts/rollback-release.sh +0 -42
  128. package/scripts/run-injection-scanner-ci.cjs +0 -83
  129. package/scripts/tests/test-authority-rejected-kinds.sh +0 -58
  130. package/scripts/tests/test-authority-watcher-diff.sh +0 -113
  131. package/scripts/tests/test-motion-provenance.sh +0 -64
  132. package/scripts/validate-frontmatter.ts +0 -409
  133. package/scripts/validate-incubator-scope.cjs +0 -133
  134. package/scripts/validate-schemas.ts +0 -401
  135. package/scripts/validate-skill-length.cjs +0 -283
  136. package/scripts/verify-version-sync.cjs +0 -30
  137. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_cycle_recap.schema.json +0 -0
  138. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_decisions_list.schema.json +0 -0
  139. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_events_tail.schema.json +0 -0
  140. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_health.schema.json +0 -0
  141. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_intel_get.schema.json +0 -0
  142. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_learnings_digest.schema.json +0 -0
  143. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phase_current.schema.json +0 -0
  144. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phases_list.schema.json +0 -0
  145. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_plans_list.schema.json +0 -0
  146. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_reflections_latest.schema.json +0 -0
  147. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_status.schema.json +0 -0
  148. /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_telemetry_query.schema.json +0 -0
  149. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_blocker.schema.json +0 -0
  150. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_decision.schema.json +0 -0
  151. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_must_have.schema.json +0 -0
  152. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/checkpoint.schema.json +0 -0
  153. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/frontmatter_update.schema.json +0 -0
  154. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/get.schema.json +0 -0
  155. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/probe_connections.schema.json +0 -0
  156. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/resolve_blocker.schema.json +0 -0
  157. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/set_status.schema.json +0 -0
  158. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/transition_stage.schema.json +0 -0
  159. /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/update_progress.schema.json +0 -0
  160. /package/{scripts/lib → sdk/primitives}/error-classifier.d.cts +0 -0
  161. /package/{scripts/lib → sdk/primitives}/iteration-budget.d.cts +0 -0
  162. /package/{scripts/lib → sdk/primitives}/jittered-backoff.d.cts +0 -0
  163. /package/{scripts/lib → sdk/primitives}/lockfile.d.cts +0 -0
@@ -1,112 +1,29 @@
1
- // scripts/lib/jittered-backoff.cjs
2
- //
3
- // Plan 20-14 — jittered exponential backoff primitive.
4
- //
5
- // Replaces fixed-interval retry sleeps across the codebase (update-check,
6
- // watch-authorities, figma probes, connection probes, hook retry loops) with
7
- // a deterministic, capped, jittered backoff curve.
8
- //
9
- // Formula:
10
- // base = min(maxMs, baseMs * factor^attempt)
11
- // delay = base * (1 + rand(-jitter, +jitter))
12
- // clamp to [0, maxMs * (1 + jitter)]
13
- //
14
- // The zero-based `attempt` counter gives `baseMs` on the first call, then
15
- // multiplies by `factor` per attempt up to the cap. Jitter is full-width
16
- // symmetric (not AWS-style equal/decorrelated) because our consumers are
17
- // single-threaded retry loops — there's no thundering-herd problem to
18
- // smooth; the jitter is cosmetic protection against synchronized retries
19
- // between siblings like the watcher + update-check running at the same
20
- // session boundary.
1
+ 'use strict';
2
+ // scripts/lib/jittered-backoff.cjs — GDD-DEPRECATION-SHIM (Plan 31-5-06, SDK-05, D-02).
21
3
  //
22
- // Defaults (baseMs=100, maxMs=30_000, factor=2, jitter=0.2) yield:
23
- // attempt 0 80-120ms
24
- // attempt 1 160-240ms
25
- // attempt 2 320-480ms
26
- // ...
27
- // attempt 9 → ~24s-30s (capped)
4
+ // Thin deprecation shim. The real implementation moved to
5
+ // sdk/primitives/jittered-backoff.cjs in Plan 31-5-04 (SDK consolidation).
6
+ // This file is re-created at the OLD path so undocumented EXTERNAL importers
7
+ // (anyone who reached into node_modules/@hegemonart/get-design-done/scripts/
8
+ // lib/jittered-backoff.cjs directly) keep working for one minor grace window.
28
9
  //
29
- // This module is `.cjs` (not `.ts`) per Plan 20-14 D-01 so it can be
30
- // `require()`d from both the `.ts` runtime (hooks, MCP server) and future
31
- // `.cjs` CLI invocations without needing `--experimental-strip-types` at
32
- // every consumer site. Types live in the paired `jittered-backoff.d.cts`.
10
+ // REMOVED IN v1.33.0 (D-02). Grace window: 1.31.5 ships with shims
11
+ // 1.32.0 still has them 1.33.0 removes them. Internal callers already use
12
+ // the sdk/ path (Plan 31-5-04/05) — this shim is external-only and 31-5-10's
13
+ // no-stale-internal-refs guard excludes files carrying the GDD-DEPRECATION-SHIM
14
+ // marker above.
33
15
  //
34
- // No external dependencies; pure Math.random + setTimeout.
35
-
36
- 'use strict';
37
-
38
- /**
39
- * Default backoff parameters — chosen to cover retry-after-Xms through
40
- * retry-after-30s with reasonable mid-range distribution.
41
- */
42
- const DEFAULTS = Object.freeze({
43
- baseMs: 100,
44
- maxMs: 30_000,
45
- factor: 2,
46
- jitter: 0.2,
47
- });
48
-
49
- /**
50
- * Compute the jittered delay in milliseconds for a given attempt number.
51
- *
52
- * @param {number} attempt zero-based attempt counter (0 = first retry)
53
- * @param {object} [opts]
54
- * @param {number} [opts.baseMs] initial delay before any jitter. Default 100.
55
- * @param {number} [opts.maxMs] maximum un-jittered base. Default 30_000.
56
- * @param {number} [opts.factor] per-attempt multiplier. Default 2.
57
- * @param {number} [opts.jitter] symmetric jitter fraction in [0, 1). Default 0.2.
58
- * @returns {number} delay in ms. Never negative. May exceed `maxMs` by up
59
- * to `jitter * maxMs` on the high side.
60
- *
61
- * Invariants:
62
- * - `delayMs(n, opts) >= 0` for every non-negative `n`.
63
- * - `delayMs(n, opts) <= maxMs * (1 + jitter)` for every non-negative `n`.
64
- * - The distribution has nonzero stddev whenever `jitter > 0`.
65
- */
66
- function delayMs(attempt, opts) {
67
- const baseMs = (opts && Number.isFinite(opts.baseMs)) ? opts.baseMs : DEFAULTS.baseMs;
68
- const maxMs = (opts && Number.isFinite(opts.maxMs)) ? opts.maxMs : DEFAULTS.maxMs;
69
- const factor = (opts && Number.isFinite(opts.factor)) ? opts.factor : DEFAULTS.factor;
70
- const jitter = (opts && Number.isFinite(opts.jitter)) ? opts.jitter : DEFAULTS.jitter;
71
-
72
- // Guard against nonsense inputs — callers that pass garbage shouldn't
73
- // cause NaN or Infinity to propagate into setTimeout.
74
- const a = Math.max(0, Number.isFinite(attempt) ? Math.floor(attempt) : 0);
75
- const safeBase = Math.max(0, baseMs);
76
- const safeMax = Math.max(safeBase, maxMs);
77
- const safeFactor = factor > 0 ? factor : DEFAULTS.factor;
78
- // Clamp jitter to [0, 1) — full-range (>=1) would allow negative values
79
- // after subtraction, which we want to forbid by invariant.
80
- const safeJitter = Math.min(0.999, Math.max(0, jitter));
81
-
82
- // Exponential growth capped at safeMax.
83
- // Math.pow with a huge attempt count can overflow to Infinity; the
84
- // Math.min picks up safeMax before Infinity escapes.
85
- const unjittered = Math.min(safeMax, safeBase * Math.pow(safeFactor, a));
86
-
87
- // Symmetric jitter in [-jitter, +jitter).
88
- // Math.random() returns [0, 1); 2r - 1 maps to [-1, 1); scale by jitter.
89
- const noise = (Math.random() * 2 - 1) * safeJitter;
90
- const delay = unjittered * (1 + noise);
91
-
92
- // Floor at zero (noise could in theory go slightly negative due to FP
93
- // precision even with safeJitter < 1, for very small unjittered values).
94
- return Math.max(0, delay);
95
- }
96
-
97
- /**
98
- * Sleep for a jittered backoff interval. Convenience wrapper around
99
- * `delayMs` + `setTimeout`.
100
- *
101
- * @param {number} attempt zero-based attempt counter
102
- * @param {object} [opts] see {@link delayMs}
103
- * @returns {Promise<number>} resolves to the actual delay that was applied
104
- */
105
- function sleep(attempt, opts) {
106
- const ms = delayMs(attempt, opts);
107
- return new Promise((resolve) => {
108
- setTimeout(() => resolve(ms), ms);
109
- });
16
+ // Emits a DeprecationWarning exactly ONCE per process: the module-level
17
+ // `warned` flag plus Node's module cache (this file is evaluated once per
18
+ // process regardless of how many times it is required).
19
+
20
+ let warned = false;
21
+ if (!warned) {
22
+ warned = true;
23
+ process.emitWarning(
24
+ 'scripts/lib/jittered-backoff.cjs is deprecated; import sdk/primitives/jittered-backoff instead. Removed in v1.33.0.',
25
+ 'DeprecationWarning',
26
+ );
110
27
  }
111
28
 
112
- module.exports = { delayMs, sleep, DEFAULTS };
29
+ module.exports = require('../../sdk/primitives/jittered-backoff.cjs');
@@ -1,201 +1,29 @@
1
- // scripts/lib/lockfile.cjs
2
- //
3
- // Plan 20-14 — PID+timestamp sibling lockfile for `.cjs` consumers.
1
+ 'use strict';
2
+ // scripts/lib/lockfile.cjs — GDD-DEPRECATION-SHIM (Plan 31-5-06, SDK-05, D-02).
4
3
  //
5
- // Algorithm mirrors scripts/lib/gdd-state/lockfile.ts (Plan 20-01):
6
- // Lock path: `${target}.lock`
7
- // Payload: { pid: number, host: string, acquired_at: ISO8601 }
8
- // Acquire: atomic `writeFileSync(..., { flag: 'wx' })`
9
- // Stale rule: pid dead (ESRCH via `kill(pid, 0)`) OR `acquired_at` older
10
- // than `staleMs` OR unparseable payload
11
- // Release: unlink; ENOENT is not an error; idempotent
4
+ // Thin deprecation shim. The real implementation moved to
5
+ // sdk/primitives/lockfile.cjs in Plan 31-5-04 (SDK consolidation).
6
+ // This file is re-created at the OLD path so undocumented EXTERNAL importers
7
+ // (anyone who reached into node_modules/@hegemonart/get-design-done/scripts/
8
+ // lib/lockfile.cjs directly) keep working for one minor grace window.
12
9
  //
13
- // Windows: AV scanners and file-indexers can hold a file briefly after
14
- // close. `wx` create may fail with EPERM/EBUSY even when the target is
15
- // free; we treat these as transient and loop (same code path as EEXIST).
10
+ // REMOVED IN v1.33.0 (D-02). Grace window: 1.31.5 ships with shims →
11
+ // 1.32.0 still has them 1.33.0 removes them. Internal callers already use
12
+ // the sdk/ path (Plan 31-5-04/05) this shim is external-only and 31-5-10's
13
+ // no-stale-internal-refs guard excludes files carrying the GDD-DEPRECATION-SHIM
14
+ // marker above.
16
15
  //
17
- // Dependency-cycle note: Plan 20-14's rate-guard + iteration-budget
18
- // consume this module, and both are required to stay dependency-light so
19
- // that hooks/budget-enforcer.ts can import them without dragging the
20
- // gdd-state MCP graph along. Hence this standalone .cjs port instead of
21
- // calling the .ts version.
22
-
23
- 'use strict';
24
-
25
- const fs = require('node:fs');
26
- const os = require('node:os');
27
-
28
- const DEFAULT_STALE_MS = 60_000;
29
- const DEFAULT_MAX_WAIT_MS = 5_000;
30
- const DEFAULT_POLL_MS = 50;
31
-
32
- /**
33
- * Acquire an advisory lock at `${path}.lock`. Returns an idempotent
34
- * async release function.
35
- *
36
- * @param {string} path path being locked (we append `.lock`)
37
- * @param {object} [opts]
38
- * @param {number} [opts.staleMs] ms after which an existing lock is stale. Default 60_000.
39
- * @param {number} [opts.maxWaitMs] total ms to wait before throwing. Default 5_000.
40
- * @param {number} [opts.pollMs] ms between retry attempts. Default 50.
41
- * @returns {Promise<() => Promise<void>>} release function
42
- * @throws {Error} with name === 'LockAcquisitionError' when maxWaitMs elapses
43
- */
44
- async function acquire(path, opts) {
45
- const o = opts || {};
46
- const staleMs = Number.isFinite(o.staleMs) ? o.staleMs : DEFAULT_STALE_MS;
47
- const maxWaitMs = Number.isFinite(o.maxWaitMs) ? o.maxWaitMs : DEFAULT_MAX_WAIT_MS;
48
- const pollMs = Number.isFinite(o.pollMs) ? o.pollMs : DEFAULT_POLL_MS;
49
-
50
- if (staleMs < 0 || maxWaitMs < 0 || pollMs < 0) {
51
- throw new Error(
52
- `lockfile.acquire: invalid options (staleMs=${staleMs}, maxWaitMs=${maxWaitMs}, pollMs=${pollMs})`,
53
- );
54
- }
55
-
56
- const lockPath = `${path}.lock`;
57
- const payload = JSON.stringify({
58
- pid: process.pid,
59
- host: os.hostname(),
60
- acquired_at: new Date().toISOString(),
61
- });
62
- const startedAt = Date.now();
63
-
64
- while (true) {
65
- try {
66
- fs.writeFileSync(lockPath, payload, { flag: 'wx', encoding: 'utf8' });
67
- return makeRelease(lockPath);
68
- } catch (err) {
69
- const code = err && typeof err === 'object' ? err.code : undefined;
70
- if (code !== 'EEXIST' && code !== 'EPERM' && code !== 'EBUSY') {
71
- throw err;
72
- }
73
- // Try to read the current holder; if it vanished between EEXIST and
74
- // read, loop immediately.
75
- const existing = readLockSafe(lockPath);
76
- if (existing === null) continue;
77
-
78
- const parsed = parseLock(existing);
79
- // Only clear when we're confident the lock is stale: the payload
80
- // parses AND the PID/age check says so. An unparseable payload is
81
- // treated as fresh — on Windows, AV/indexer can transiently deny
82
- // reads (EACCES/EPERM/EBUSY), and clearing under that condition
83
- // would let two writers race and lose increments.
84
- if (parsed !== null && isStale(parsed, staleMs)) {
85
- // Clear stale lock; race-tolerant — if it's already gone we get
86
- // ENOENT, no-op.
87
- try { fs.unlinkSync(lockPath); } catch { /* ignore */ }
88
- continue;
89
- }
90
-
91
- if (Date.now() - startedAt >= maxWaitMs) {
92
- const e = new Error(
93
- `lockfile: failed to acquire ${lockPath} within ${maxWaitMs}ms (held by ${existing})`,
94
- );
95
- e.name = 'LockAcquisitionError';
96
- e.lockPath = lockPath;
97
- e.holder = existing;
98
- e.waitedMs = Date.now() - startedAt;
99
- throw e;
100
- }
101
- await sleep(pollMs);
102
- }
103
- }
104
- }
105
-
106
- function makeRelease(lockPath) {
107
- let released = false;
108
- return async function release() {
109
- if (released) return;
110
- released = true;
111
- try {
112
- fs.unlinkSync(lockPath);
113
- } catch (err) {
114
- const code = err && typeof err === 'object' ? err.code : undefined;
115
- if (code === 'ENOENT') return; // idempotent — already gone
116
- if (code === 'EPERM' || code === 'EBUSY') {
117
- // Windows AV/indexer: retry once.
118
- await sleep(50);
119
- try {
120
- if (fs.existsSync(lockPath)) fs.unlinkSync(lockPath);
121
- } catch { /* give up; stale-detection will reclaim */ }
122
- return;
123
- }
124
- // Any other errno: swallow. Best-effort cleanup; stale-age check
125
- // will eventually reclaim the lock.
126
- }
127
- };
128
- }
129
-
130
- function readLockSafe(p) {
131
- try {
132
- return fs.readFileSync(p, 'utf8');
133
- } catch (err) {
134
- const code = err && typeof err === 'object' ? err.code : undefined;
135
- if (code === 'ENOENT') return null;
136
- return '<unreadable>';
137
- }
138
- }
139
-
140
- function parseLock(raw) {
141
- try {
142
- const obj = JSON.parse(raw);
143
- if (
144
- obj && typeof obj === 'object' &&
145
- typeof obj.pid === 'number' &&
146
- typeof obj.host === 'string' &&
147
- typeof obj.acquired_at === 'string'
148
- ) {
149
- return obj;
150
- }
151
- return null;
152
- } catch {
153
- return null;
154
- }
155
- }
156
-
157
- function isStale(payload, staleMs) {
158
- if (!isPidAlive(payload.pid, payload.host)) return true;
159
- const t = Date.parse(payload.acquired_at);
160
- if (!Number.isFinite(t)) return true;
161
- return Date.now() - t > staleMs;
162
- }
163
-
164
- function isPidAlive(pid, host) {
165
- if (host !== os.hostname()) return true; // can't introspect other hosts
166
- if (pid === process.pid) return true;
167
- try {
168
- process.kill(pid, 0); // signal 0 = validate, don't deliver
169
- return true;
170
- } catch (err) {
171
- const code = err && typeof err === 'object' ? err.code : undefined;
172
- if (code === 'ESRCH') return false;
173
- // EPERM / EACCES: process exists but is unsignalable; treat as alive.
174
- return true;
175
- }
176
- }
177
-
178
- function sleep(ms) {
179
- return new Promise((resolve) => setTimeout(resolve, ms));
180
- }
16
+ // Emits a DeprecationWarning exactly ONCE per process: the module-level
17
+ // `warned` flag plus Node's module cache (this file is evaluated once per
18
+ // process regardless of how many times it is required).
181
19
 
182
- /**
183
- * `fs.renameSync` wrapper that retries once on Windows EPERM/EBUSY/EACCES.
184
- * AV scanners and the file-indexer can briefly hold a destination open
185
- * after another process closed it, causing rename to fail even when the
186
- * advisory lock is correctly held.
187
- *
188
- * Mirrors the inline retry in scripts/lib/gdd-state/index.ts mutate().
189
- */
190
- async function renameWithRetry(from, to) {
191
- try {
192
- fs.renameSync(from, to);
193
- } catch (err) {
194
- const code = err && typeof err === 'object' ? err.code : undefined;
195
- if (code !== 'EPERM' && code !== 'EBUSY' && code !== 'EACCES') throw err;
196
- await sleep(50);
197
- fs.renameSync(from, to);
198
- }
20
+ let warned = false;
21
+ if (!warned) {
22
+ warned = true;
23
+ process.emitWarning(
24
+ 'scripts/lib/lockfile.cjs is deprecated; import sdk/primitives/lockfile instead. Removed in v1.33.0.',
25
+ 'DeprecationWarning',
26
+ );
199
27
  }
200
28
 
201
- module.exports = { acquire, renameWithRetry };
29
+ module.exports = require('../../sdk/primitives/lockfile.cjs');
@@ -29,7 +29,7 @@
29
29
  // process.stderr); the logger contract never propagates event-stream
30
30
  // failures back to the caller.
31
31
 
32
- import { appendEvent } from '../event-stream/index.ts';
32
+ import { appendEvent } from '../../../sdk/event-stream/index.ts';
33
33
  import { ConsoleSink, JsonlSink, MultiSink } from './sinks.ts';
34
34
  import {
35
35
  LEVEL_ORDER,
@@ -59,7 +59,7 @@ function getAppendEvent() {
59
59
  // with --experimental-strip-types (or Node 24 built-in TS) can
60
60
  // require it. If require fails (e.g., older runtime, missing
61
61
  // module), fall through to the no-op.
62
- const m = require('../event-stream');
62
+ const m = require('../../../sdk/event-stream');
63
63
  if (m && typeof m.appendEvent === 'function') return m.appendEvent;
64
64
  } catch {
65
65
  // Swallow — best-effort telemetry. Losing one verdict is
@@ -6,7 +6,7 @@
6
6
  * `.design/telemetry/trajectories/<cycle>.jsonl` files (agent trace
7
7
  * lines per Phase 22).
8
8
  *
9
- * JSONL discipline (same as scripts/lib/event-stream/reader.ts):
9
+ * JSONL discipline (same as sdk/event-stream/reader.ts):
10
10
  * - One JSON object per line.
11
11
  * - Blank lines / whitespace-only lines ignored silently.
12
12
  * - Malformed lines tolerated — counted in skipped_count, NOT thrown.
@@ -23,11 +23,11 @@
23
23
  //
24
24
  // NEVER throws — every failure becomes a `PipelineResult`.
25
25
 
26
- import { appendEvent } from '../event-stream/index.ts';
27
- import type { BaseEvent } from '../event-stream/index.ts';
26
+ import { appendEvent } from '../../../sdk/event-stream/index.ts';
27
+ import type { BaseEvent } from '../../../sdk/event-stream/index.ts';
28
28
  import { getLogger } from '../logger/index.ts';
29
- import { transition as defaultTransition, TransitionGateFailed } from '../gdd-state/index.ts';
30
- import { ValidationError } from '../gdd-errors/index.ts';
29
+ import { transition as defaultTransition, TransitionGateFailed } from '../../../sdk/state/index.ts';
30
+ import { ValidationError } from '../../../sdk/errors/index.ts';
31
31
 
32
32
  import {
33
33
  STAGE_ORDER,
@@ -15,7 +15,7 @@
15
15
  // name is in the set. Unknown stage names in `skipStages` are
16
16
  // tolerated (no-op) — the filter is a membership check.
17
17
 
18
- import { ValidationError } from '../gdd-errors/index.ts';
18
+ import { ValidationError } from '../../../sdk/errors/index.ts';
19
19
  import type { Stage } from './types.ts';
20
20
 
21
21
  /**
@@ -27,7 +27,7 @@ const DEFAULT_THRESHOLD = 3; // D-11 — '>= 3 agents'
27
27
  */
28
28
  function getAppendEvent() {
29
29
  try {
30
- const m = require('../event-stream');
30
+ const m = require('../../../sdk/event-stream');
31
31
  if (m && typeof m.appendEvent === 'function') return m.appendEvent;
32
32
  } catch { /* swallow — event-stream not on path */ }
33
33
  return function noopAppend(_ev) {};
@@ -20,7 +20,7 @@
20
20
  // `resetAt` wins. This matches the D-01 rule in the plan.
21
21
  //
22
22
  // State-file writes are atomic (temp + rename) and coordinated by
23
- // scripts/lib/lockfile.cjs so two child processes hitting `ingestHeaders`
23
+ // sdk/primitives/lockfile.cjs so two child processes hitting `ingestHeaders`
24
24
  // concurrently can never corrupt the file. The lock is scoped to the
25
25
  // state file, not to the provider directory, so different providers can
26
26
  // update in parallel.
@@ -30,7 +30,7 @@
30
30
  const fs = require('node:fs');
31
31
  const path = require('node:path');
32
32
 
33
- const { acquire, renameWithRetry } = require('./lockfile.cjs');
33
+ const { acquire, renameWithRetry } = require('../../sdk/primitives/lockfile.cjs');
34
34
 
35
35
  const STATE_DIR_REL = path.join('.design', 'rate-limits');
36
36
  const LOCK_MAX_WAIT_MS = 3_000;
@@ -0,0 +1,142 @@
1
+ /**
2
+ * recipe-loader.cjs — recipes/ scaffold loader (Plan 31-5-03, RECIPE-01 / SC#14).
3
+ *
4
+ * The top-level recipes/ directory ships EMPTY of recipes; it is populated
5
+ * downstream by Phase 32 (skill-trigger recipes), Phase 33.6 (per-provider),
6
+ * Phase 26 (per-runtime/per-model), and Phase 23.5 (bandit-arm shape). This
7
+ * module fixes the loading contract those phases build against so each does not
8
+ * reinvent it. Modelled on Storybloq's src/autonomous/recipes/ loader.ts.
9
+ *
10
+ * Contract:
11
+ * loadRecipe(name, opts?) -> Recipe
12
+ * name : recipe stem; resolves <repoRoot>/recipes/<name>.json
13
+ * opts : { recipesDir, schemaPath, fs } — all injectable for tests
14
+ * returns : the validated, parsed Recipe object
15
+ * throws : Error('recipe not found: <name>') if the file is absent
16
+ * Error('recipe <name> failed schema validation: …') if invalid
17
+ *
18
+ * Cache (SC#14 "caches by SHA"):
19
+ * Keyed by name + ':' + sha256(fileBytes). Each call reads the file once to
20
+ * compute the content hash; on a HIT (unchanged bytes) it returns the cached
21
+ * object WITHOUT re-parsing / re-validating. On a MISS (changed bytes) it
22
+ * parses + validates + stores. Keying by content SHA — not just name — means
23
+ * an edited recipe is correctly re-validated.
24
+ *
25
+ * Empty-dir safety: this module requires cleanly when recipes/ is empty, and a
26
+ * directory listing of an empty (just-.gitkeep) dir does not throw. loadRecipe
27
+ * of a missing name throws the clear not-found error — distinct from "the empty
28
+ * dir is broken" (it is a valid scaffold state).
29
+ *
30
+ * Uses the repo's existing `ajv` dependency (package.json "ajv": "^8.18.0") —
31
+ * no new dependency. The schema is compiled once per schemaPath (singleton).
32
+ */
33
+
34
+ 'use strict';
35
+
36
+ const fs = require('node:fs');
37
+ const path = require('node:path');
38
+ const crypto = require('node:crypto');
39
+ const Ajv = require('ajv');
40
+
41
+ // recipe-loader.cjs lives in scripts/lib/ → two levels up is the repo root.
42
+ const REPO_ROOT = path.resolve(__dirname, '..', '..');
43
+ const DEFAULT_RECIPES_DIR = path.join(REPO_ROOT, 'recipes');
44
+ const DEFAULT_SCHEMA_PATH = path.join(REPO_ROOT, 'reference', 'schemas', 'recipe.schema.json');
45
+
46
+ // Ajv 8 CJS: require('ajv') is (or wraps) the constructor.
47
+ const AjvCtor = Ajv.default || Ajv;
48
+
49
+ // SHA-keyed cache: `name + ':' + sha256(bytes)` → parsed Recipe object.
50
+ const cache = new Map();
51
+
52
+ // Compiled-validator singletons, keyed by resolved schemaPath. The schema is a
53
+ // small trusted local file, so allErrors:true (full error list → clear message)
54
+ // is safe here — unlike the untrusted-HTTP receiver which fails fast.
55
+ const validators = new Map();
56
+
57
+ // Test-observability counter: increments once per parse+validate (i.e. per
58
+ // cache MISS). A cache HIT does not bump it. Tests assert on the delta to prove
59
+ // SHA-keyed hit/miss semantics without depending on raw read counts (a hit must
60
+ // still read the file once to hash it).
61
+ let _validations = 0;
62
+
63
+ /**
64
+ * Get (compiling once) the validator for a schema path.
65
+ * @param {string} schemaPath
66
+ * @param {typeof fs} fsImpl
67
+ * @returns {import('ajv').ValidateFunction}
68
+ */
69
+ function getValidator(schemaPath, fsImpl) {
70
+ const resolved = path.resolve(schemaPath);
71
+ let validate = validators.get(resolved);
72
+ if (validate) return validate;
73
+
74
+ const schema = JSON.parse(fsImpl.readFileSync(resolved, 'utf8'));
75
+ const ajv = new AjvCtor({ allErrors: true, strict: false });
76
+ validate = ajv.compile(schema);
77
+ validators.set(resolved, validate);
78
+ return validate;
79
+ }
80
+
81
+ /**
82
+ * Load + validate a recipe by name.
83
+ * @param {string} name - recipe stem; resolves <recipesDir>/<name>.json
84
+ * @param {{ recipesDir?: string, schemaPath?: string, fs?: typeof fs }} [opts]
85
+ * @returns {Record<string, unknown>} the validated, parsed Recipe object
86
+ */
87
+ function loadRecipe(name, opts = {}) {
88
+ if (typeof name !== 'string' || name.length === 0) {
89
+ throw new Error('recipe name must be a non-empty string');
90
+ }
91
+
92
+ const fsImpl = opts.fs || fs;
93
+ const dir = opts.recipesDir || DEFAULT_RECIPES_DIR;
94
+ const schemaPath = opts.schemaPath || DEFAULT_SCHEMA_PATH;
95
+ const file = path.join(dir, name + '.json');
96
+
97
+ if (!fsImpl.existsSync(file)) {
98
+ throw new Error('recipe not found: ' + name);
99
+ }
100
+
101
+ // Read once per call to compute the content hash (the cache key).
102
+ const bytes = fsImpl.readFileSync(file);
103
+ const sha = crypto.createHash('sha256').update(bytes).digest('hex');
104
+ const key = name + ':' + sha;
105
+
106
+ // Cache HIT: unchanged bytes → return cached object, skip parse + validate.
107
+ if (cache.has(key)) {
108
+ return cache.get(key);
109
+ }
110
+
111
+ // Cache MISS: parse + validate + store.
112
+ let parsed;
113
+ try {
114
+ parsed = JSON.parse(bytes.toString('utf8'));
115
+ } catch (err) {
116
+ throw new Error('recipe ' + name + ' is not valid JSON: ' + err.message);
117
+ }
118
+
119
+ const validate = getValidator(schemaPath, fsImpl);
120
+ _validations += 1;
121
+ const ok = validate(parsed) === true;
122
+ if (!ok) {
123
+ throw new Error(
124
+ 'recipe ' + name + ' failed schema validation: ' + JSON.stringify(validate.errors || []),
125
+ );
126
+ }
127
+
128
+ cache.set(key, parsed);
129
+ return parsed;
130
+ }
131
+
132
+ /** Test hook: clear the SHA cache (does not drop compiled validators). */
133
+ function _clearCache() {
134
+ cache.clear();
135
+ }
136
+
137
+ /** Test hook: introspect parse+validate counts (one per cache MISS). */
138
+ function _stats() {
139
+ return { validations: _validations, cacheSize: cache.size };
140
+ }
141
+
142
+ module.exports = { loadRecipe, _clearCache, _stats };
@@ -38,7 +38,7 @@ import {
38
38
  OperationFailedError,
39
39
  StateConflictError,
40
40
  type GDDError,
41
- } from '../gdd-errors/index.ts';
41
+ } from '../../../sdk/errors/index.ts';
42
42
 
43
43
  /**
44
44
  * Build an absolute path to a repo-root-relative file. We can't use
@@ -68,7 +68,7 @@ const REPO_ROOT = findRepoRoot();
68
68
  */
69
69
  const nodeRequire = createRequire(join(REPO_ROOT, 'package.json'));
70
70
  const transportClassifier = nodeRequire(
71
- resolve(REPO_ROOT, 'scripts/lib/error-classifier.cjs'),
71
+ resolve(REPO_ROOT, 'sdk/primitives/error-classifier.cjs'),
72
72
  ) as {
73
73
  classify: (err: unknown) => {
74
74
  reason: string;
@@ -375,7 +375,7 @@ export function mapSdkError(err: unknown): MappedSdkError {
375
375
  }
376
376
 
377
377
  // 9. Transport-layer classification (ECONNRESET, ETIMEDOUT, etc.).
378
- // Delegate to scripts/lib/error-classifier.cjs which knows the errno
378
+ // Delegate to sdk/primitives/error-classifier.cjs which knows the errno
379
379
  // vocabulary. Only trust its `retryable` flag for transient network
380
380
  // classes — other classes were already handled above.
381
381
  const classified = transportClassifier.classify(err);
@@ -26,8 +26,8 @@
26
26
  // (always; payload status mirrors SessionResult.status). Optional
27
27
  // `session.budget_exceeded` emitted when the budget trips.
28
28
 
29
- import { appendEvent } from '../event-stream/index.ts';
30
- import type { BaseEvent } from '../event-stream/index.ts';
29
+ import { appendEvent } from '../../../sdk/event-stream/index.ts';
30
+ import type { BaseEvent } from '../../../sdk/event-stream/index.ts';
31
31
  import { sanitize as defaultSanitize } from '../prompt-sanitizer/index.ts';
32
32
 
33
33
  import { mapSdkError } from './errors.ts';
@@ -64,7 +64,7 @@ function _findRepoRoot(): string {
64
64
  const _REPO_ROOT = _findRepoRoot();
65
65
  const _nodeRequire = createRequire(_join(_REPO_ROOT, 'package.json'));
66
66
  const jitteredBackoff = _nodeRequire(
67
- _resolve(_REPO_ROOT, 'scripts/lib/jittered-backoff.cjs'),
67
+ _resolve(_REPO_ROOT, 'sdk/primitives/jittered-backoff.cjs'),
68
68
  ) as {
69
69
  delayMs: (attempt: number, opts?: { baseMs?: number; maxMs?: number; factor?: number; jitter?: number }) => number;
70
70
  };
@@ -2,7 +2,7 @@
2
2
  // transcript writer for Phase 21 headless Agent SDK sessions
3
3
  // (Plan 21-01 Task 4).
4
4
  //
5
- // Design mirrors scripts/lib/event-stream/writer.ts but is scoped to one
5
+ // Design mirrors sdk/event-stream/writer.ts but is scoped to one
6
6
  // session per file rather than the global telemetry stream. Each session
7
7
  // owns a dedicated `.design/sessions/<ISO>-<stage>.jsonl` file; the
8
8
  // filename is stable for the full run and survives retries (retries
@@ -21,7 +21,7 @@
21
21
  // * Plan 21-01 `session-runner` — computes `allowedTools` for each session.
22
22
  // * Plan 21-05 `pipeline-runner` — picks the correct scope per stage.
23
23
 
24
- import { ValidationError } from '../gdd-errors/index.ts';
24
+ import { ValidationError } from '../../../sdk/errors/index.ts';
25
25
  import type { Scope, ScopeInput, ScopeViolation, Stage } from './types.ts';
26
26
  import {
27
27
  NATIVE_TOOLS,