@hegemonart/get-design-done 1.31.0 → 1.32.0
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +75 -0
- package/NOTICE +262 -0
- package/README.md +13 -1
- package/SKILL.md +4 -0
- package/agents/design-authority-watcher.md +1 -1
- package/agents/perf-analyzer.md +2 -2
- package/bin/gdd-mcp +78 -0
- package/bin/gdd-sdk +34 -24
- package/bin/gdd-state-mcp +78 -0
- package/{README.de.md → docs/i18n/README.de.md} +1 -1
- package/{README.fr.md → docs/i18n/README.fr.md} +1 -1
- package/{README.it.md → docs/i18n/README.it.md} +1 -1
- package/{README.ja.md → docs/i18n/README.ja.md} +1 -1
- package/{README.ko.md → docs/i18n/README.ko.md} +1 -1
- package/{README.zh-CN.md → docs/i18n/README.zh-CN.md} +1 -1
- package/hooks/_hook-emit.js +1 -1
- package/hooks/budget-enforcer.ts +5 -5
- package/hooks/context-exhaustion.ts +2 -2
- package/hooks/gdd-precompact-snapshot.js +3 -3
- package/hooks/gdd-read-injection-scanner.ts +2 -2
- package/hooks/gdd-sessionstart-recap.js +1 -1
- package/hooks/gdd-turn-closeout.js +1 -1
- package/hooks/hooks.json +9 -0
- package/hooks/inject-using-gdd.sh +72 -0
- package/hooks/run-hook.cmd +35 -0
- package/package.json +20 -9
- package/recipes/.gitkeep +0 -0
- package/reference/schemas/events.schema.json +63 -1
- package/reference/schemas/recipe.schema.json +33 -0
- package/scripts/cli/gdd-events.mjs +5 -5
- package/scripts/lib/cache/gdd-cache-manager.cjs +1 -1
- package/scripts/lib/cli/index.ts +22 -160
- package/scripts/lib/connection-probe/index.cjs +1 -1
- package/scripts/lib/discuss-parallel-runner/aggregator.ts +1 -1
- package/scripts/lib/discuss-parallel-runner/index.ts +1 -1
- package/scripts/lib/error-classifier.cjs +24 -227
- package/scripts/lib/event-stream/index.ts +25 -193
- package/scripts/lib/gdd-errors/index.ts +24 -213
- package/scripts/lib/gdd-state/index.ts +23 -161
- package/scripts/lib/health-mirror/index.cjs +79 -1
- package/scripts/lib/iteration-budget.cjs +23 -199
- package/scripts/lib/jittered-backoff.cjs +24 -107
- package/scripts/lib/lockfile.cjs +23 -195
- package/scripts/lib/logger/index.ts +1 -1
- package/scripts/lib/parallelism-engine/concurrency-tuner.cjs +1 -1
- package/scripts/lib/perf-analyzer/index.cjs +1 -1
- package/scripts/lib/pipeline-runner/index.ts +4 -4
- package/scripts/lib/pipeline-runner/state-machine.ts +1 -1
- package/scripts/lib/prompt-dedup/index.cjs +1 -1
- package/scripts/lib/rate-guard.cjs +2 -2
- package/scripts/lib/recipe-loader.cjs +142 -0
- package/scripts/lib/session-runner/errors.ts +3 -3
- package/scripts/lib/session-runner/index.ts +3 -3
- package/scripts/lib/session-runner/transcript.ts +1 -1
- package/scripts/lib/tool-scoping/index.ts +1 -1
- package/scripts/mcp-servers/gdd-mcp/server.ts +29 -311
- package/scripts/mcp-servers/gdd-state/server.ts +28 -282
- package/sdk/README.md +45 -0
- package/{scripts/lib → sdk}/cli/commands/audit.ts +3 -3
- package/{scripts/lib → sdk}/cli/commands/init.ts +3 -3
- package/{scripts/lib → sdk}/cli/commands/query.ts +4 -4
- package/{scripts/lib → sdk}/cli/commands/run.ts +5 -5
- package/{scripts/lib → sdk}/cli/commands/stage.ts +5 -5
- package/sdk/cli/index.js +8091 -0
- package/sdk/cli/index.ts +172 -0
- package/{scripts/lib → sdk}/cli/parse-args.ts +2 -2
- package/{scripts/lib/gdd-errors → sdk/errors}/classification.ts +1 -1
- package/sdk/errors/index.ts +218 -0
- package/{scripts/lib → sdk}/event-stream/emitter.ts +1 -1
- package/sdk/event-stream/index.ts +197 -0
- package/{scripts/lib → sdk}/event-stream/reader.ts +1 -1
- package/{scripts/lib → sdk}/event-stream/types.ts +2 -2
- package/{scripts/lib → sdk}/event-stream/writer.ts +1 -1
- package/sdk/index.ts +19 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/README.md +3 -3
- package/sdk/mcp/gdd-mcp/server.js +1966 -0
- package/sdk/mcp/gdd-mcp/server.ts +325 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_cycle_recap.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_decisions_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_events_tail.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_health.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_intel_get.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_learnings_digest.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phase_current.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phases_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_plans_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_reflections_latest.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_status.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_telemetry_query.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/index.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/shared.ts +3 -3
- package/sdk/mcp/gdd-state/server.js +2790 -0
- package/sdk/mcp/gdd-state/server.ts +294 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_blocker.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_decision.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_must_have.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/checkpoint.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/frontmatter_update.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/get.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/index.ts +1 -1
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/probe_connections.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/resolve_blocker.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/set_status.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/shared.ts +8 -8
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/transition_stage.ts +4 -4
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/update_progress.ts +2 -2
- package/sdk/primitives/error-classifier.cjs +232 -0
- package/sdk/primitives/iteration-budget.cjs +205 -0
- package/sdk/primitives/jittered-backoff.cjs +112 -0
- package/sdk/primitives/lockfile.cjs +201 -0
- package/{scripts/lib/gdd-state → sdk/state}/gates.ts +1 -1
- package/sdk/state/index.ts +167 -0
- package/{scripts/lib/gdd-state → sdk/state}/lockfile.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/mutator.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/parser.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/types.ts +4 -4
- package/skills/audit/SKILL.md +13 -0
- package/skills/brief/SKILL.md +25 -0
- package/skills/design/SKILL.md +17 -0
- package/skills/discuss/SKILL.md +13 -0
- package/skills/explore/SKILL.md +17 -0
- package/skills/health/SKILL.md +6 -0
- package/skills/plan/SKILL.md +25 -0
- package/skills/quality-gate/SKILL.md +2 -2
- package/skills/router/SKILL.md +4 -0
- package/skills/router/router-pick-emitter.md +78 -0
- package/skills/using-gdd/SKILL.md +78 -0
- package/skills/verify/SKILL.md +17 -0
- package/scripts/aggregate-agent-metrics.ts +0 -282
- package/scripts/bootstrap-manifest.txt +0 -3
- package/scripts/bootstrap.sh +0 -80
- package/scripts/build-distribution-bundles.cjs +0 -549
- package/scripts/build-intel.cjs +0 -486
- package/scripts/codegen-schema-types.ts +0 -149
- package/scripts/detect-stale-refs.cjs +0 -107
- package/scripts/e2e/run-headless.ts +0 -514
- package/scripts/extract-changelog-section.cjs +0 -58
- package/scripts/gsd-cleanup-incubator.cjs +0 -367
- package/scripts/injection-patterns.cjs +0 -58
- package/scripts/lint-agentskills-spec.cjs +0 -457
- package/scripts/release-smoke-test.cjs +0 -200
- package/scripts/rollback-release.sh +0 -42
- package/scripts/run-injection-scanner-ci.cjs +0 -83
- package/scripts/tests/test-authority-rejected-kinds.sh +0 -58
- package/scripts/tests/test-authority-watcher-diff.sh +0 -113
- package/scripts/tests/test-motion-provenance.sh +0 -64
- package/scripts/validate-frontmatter.ts +0 -409
- package/scripts/validate-incubator-scope.cjs +0 -133
- package/scripts/validate-schemas.ts +0 -401
- package/scripts/validate-skill-length.cjs +0 -283
- package/scripts/verify-version-sync.cjs +0 -30
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_cycle_recap.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_decisions_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_events_tail.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_health.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_intel_get.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_learnings_digest.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phase_current.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phases_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_plans_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_reflections_latest.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_status.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_telemetry_query.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_blocker.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_decision.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_must_have.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/checkpoint.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/frontmatter_update.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/get.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/probe_connections.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/resolve_blocker.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/set_status.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/transition_stage.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/update_progress.schema.json +0 -0
- /package/{scripts/lib → sdk/primitives}/error-classifier.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/iteration-budget.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/jittered-backoff.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/lockfile.d.cts +0 -0
|
@@ -8,13 +8,14 @@
|
|
|
8
8
|
// Surface:
|
|
9
9
|
// async getHealthChecks(rootDir) → { checks: HealthCheck[] }
|
|
10
10
|
//
|
|
11
|
-
// The
|
|
11
|
+
// The 7 checks (in stable order) are:
|
|
12
12
|
// 1. claude_md — CLAUDE.md presence
|
|
13
13
|
// 2. planning_dir — .planning/ presence
|
|
14
14
|
// 3. design_dir — .design/ presence
|
|
15
15
|
// 4. package_json — package.json present AND parseable
|
|
16
16
|
// 5. issue_reporter — kill-switch state (Plan 30-06 / D-08)
|
|
17
17
|
// 6. figma_extract — extract readiness + Free-tier signal (Plan 31-09)
|
|
18
|
+
// 7. skill_discipline — using-gdd bootstrap + SessionStart inject (Plan 32-07)
|
|
18
19
|
//
|
|
19
20
|
// Check 5 was added in Plan 30-06 — surfaces the report-issue kill-switch
|
|
20
21
|
// (env or config disable) so users can verify why the command is
|
|
@@ -34,6 +35,17 @@
|
|
|
34
35
|
// logged, or placed in the detail. The Free-tier state is derived from a LOCAL
|
|
35
36
|
// signal only (a prior pull's _meta.json recording a 403/skip on the Variables
|
|
36
37
|
// endpoint) — never a live network call (health-mirror is pure read-only).
|
|
38
|
+
//
|
|
39
|
+
// Check 7 was added in Plan 32-07 — surfaces whether the skill-discipline
|
|
40
|
+
// bootstrap (Phase 32) is live so a user can confirm the using-gdd SessionStart
|
|
41
|
+
// inject is wired. The detail line is one of three exact strings:
|
|
42
|
+
// - "skill-discipline: ready" (using-gdd present AND hooks.json
|
|
43
|
+
// SessionStart wires inject-using-gdd.sh)
|
|
44
|
+
// - "skill-discipline: missing using-gdd" (skills/using-gdd/SKILL.md absent)
|
|
45
|
+
// - "skill-discipline: hook not wired" (skill present but no SessionStart
|
|
46
|
+
// inject-using-gdd entry)
|
|
47
|
+
// status: 'ok' when ready, 'warn' otherwise. PURE read-only (rootDir-relative
|
|
48
|
+
// file + JSON inspection only) — NEVER throws, NEVER networks.
|
|
37
49
|
|
|
38
50
|
const fs = require('node:fs');
|
|
39
51
|
const path = require('node:path');
|
|
@@ -174,9 +186,75 @@ async function getHealthChecks(rootDir) {
|
|
|
174
186
|
checks.push({ name: 'figma_extract', status, detail });
|
|
175
187
|
}
|
|
176
188
|
|
|
189
|
+
// 7. skill_discipline — using-gdd bootstrap + SessionStart inject (Plan 32-07).
|
|
190
|
+
// Reports exactly one of three states. PURE read-only: file existence +
|
|
191
|
+
// hooks.json JSON inspection only. NEVER throws, NEVER networks (every read
|
|
192
|
+
// is wrapped defensively like the figma_extract check above).
|
|
193
|
+
{
|
|
194
|
+
const skillPresent = fileExists(
|
|
195
|
+
path.join(rootDir, 'skills', 'using-gdd', 'SKILL.md')
|
|
196
|
+
);
|
|
197
|
+
const hookWired = skillPresent && sessionStartWiresInject(rootDir);
|
|
198
|
+
|
|
199
|
+
let detail;
|
|
200
|
+
let status;
|
|
201
|
+
if (!skillPresent) {
|
|
202
|
+
detail = 'skill-discipline: missing using-gdd';
|
|
203
|
+
status = 'warn';
|
|
204
|
+
} else if (!hookWired) {
|
|
205
|
+
detail = 'skill-discipline: hook not wired';
|
|
206
|
+
status = 'warn';
|
|
207
|
+
} else {
|
|
208
|
+
detail = 'skill-discipline: ready';
|
|
209
|
+
status = 'ok';
|
|
210
|
+
}
|
|
211
|
+
checks.push({ name: 'skill_discipline', status, detail });
|
|
212
|
+
}
|
|
213
|
+
|
|
177
214
|
return { checks };
|
|
178
215
|
}
|
|
179
216
|
|
|
217
|
+
/**
|
|
218
|
+
* Does hooks/hooks.json wire the inject-using-gdd SessionStart entry?
|
|
219
|
+
* PURE read-only JSON inspection. Defensive: a missing/garbage hooks.json or an
|
|
220
|
+
* unexpected shape returns false (→ "hook not wired") rather than throwing — the
|
|
221
|
+
* health probe must never crash on this check. NEVER networks.
|
|
222
|
+
*
|
|
223
|
+
* @param {string} rootDir project root passed to getHealthChecks
|
|
224
|
+
* @returns {boolean} true iff a SessionStart hook command references inject-using-gdd
|
|
225
|
+
*/
|
|
226
|
+
function sessionStartWiresInject(rootDir) {
|
|
227
|
+
try {
|
|
228
|
+
const p = path.join(rootDir, 'hooks', 'hooks.json');
|
|
229
|
+
let hooks;
|
|
230
|
+
try {
|
|
231
|
+
hooks = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
232
|
+
} catch {
|
|
233
|
+
return false; // missing/garbage hooks.json → not wired
|
|
234
|
+
}
|
|
235
|
+
const sessionStart =
|
|
236
|
+
hooks && hooks.hooks && Array.isArray(hooks.hooks.SessionStart)
|
|
237
|
+
? hooks.hooks.SessionStart
|
|
238
|
+
: [];
|
|
239
|
+
for (const entry of sessionStart) {
|
|
240
|
+
const inner = entry && Array.isArray(entry.hooks) ? entry.hooks : [];
|
|
241
|
+
for (const h of inner) {
|
|
242
|
+
if (
|
|
243
|
+
h &&
|
|
244
|
+
typeof h.command === 'string' &&
|
|
245
|
+
/inject-using-gdd/.test(h.command)
|
|
246
|
+
) {
|
|
247
|
+
return true;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
} catch {
|
|
253
|
+
// Absolute safety net — never crash the health probe on this check.
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
180
258
|
/**
|
|
181
259
|
* Free-tier signal (LOCAL only — never a network call). The raw-pull stage
|
|
182
260
|
* (scripts/lib/figma-extract/pull.cjs) writes a _meta.json per file key under
|
|
@@ -1,205 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// Plan 20-14 — bounded fix-loop iteration budget.
|
|
1
|
+
'use strict';
|
|
2
|
+
// scripts/lib/iteration-budget.cjs — GDD-DEPRECATION-SHIM (Plan 31-5-06, SDK-05, D-02).
|
|
4
3
|
//
|
|
5
|
-
//
|
|
6
|
-
// iteration
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// Thin deprecation shim. The real implementation moved to
|
|
5
|
+
// sdk/primitives/iteration-budget.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/iteration-budget.cjs directly) keep working for one minor grace window.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
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.
|
|
15
15
|
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
'use strict';
|
|
20
|
-
|
|
21
|
-
const fs = require('node:fs');
|
|
22
|
-
const path = require('node:path');
|
|
23
|
-
|
|
24
|
-
const { acquire, renameWithRetry } = require('./lockfile.cjs');
|
|
25
|
-
|
|
26
|
-
const STATE_PATH_REL = path.join('.design', 'iteration-budget.json');
|
|
27
|
-
const DEFAULT_BUDGET = 50;
|
|
28
|
-
const LOCK_MAX_WAIT_MS = 5_000;
|
|
29
|
-
|
|
30
|
-
/** Error thrown by `consume()` when the remaining budget would go below 0. */
|
|
31
|
-
class IterationBudgetExhaustedError extends Error {
|
|
32
|
-
constructor(amount, state) {
|
|
33
|
-
super(
|
|
34
|
-
`IterationBudgetExhausted: cannot consume ${amount} — remaining=${state.remaining}, budget=${state.budget}, consumed=${state.consumed}. Caller must surface to user (fix-loop has stopped converging).`,
|
|
35
|
-
);
|
|
36
|
-
this.name = 'IterationBudgetExhaustedError';
|
|
37
|
-
this.amount = amount;
|
|
38
|
-
this.state = state;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function stateAbsPath() {
|
|
43
|
-
return path.join(process.cwd(), STATE_PATH_REL);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Read and validate the state file. Returns null on missing/corrupt. */
|
|
47
|
-
function readStateSync() {
|
|
48
|
-
const p = stateAbsPath();
|
|
49
|
-
if (!fs.existsSync(p)) return null;
|
|
50
|
-
try {
|
|
51
|
-
const raw = fs.readFileSync(p, 'utf8');
|
|
52
|
-
const parsed = JSON.parse(raw);
|
|
53
|
-
if (
|
|
54
|
-
parsed &&
|
|
55
|
-
typeof parsed === 'object' &&
|
|
56
|
-
Number.isInteger(parsed.budget) && parsed.budget >= 0 &&
|
|
57
|
-
Number.isInteger(parsed.remaining) && parsed.remaining >= 0 &&
|
|
58
|
-
Number.isInteger(parsed.consumed) && parsed.consumed >= 0 &&
|
|
59
|
-
Number.isInteger(parsed.refunded) && parsed.refunded >= 0 &&
|
|
60
|
-
typeof parsed.updatedAt === 'string'
|
|
61
|
-
) {
|
|
62
|
-
return parsed;
|
|
63
|
-
}
|
|
64
|
-
return null;
|
|
65
|
-
} catch {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/** Atomically write state under a file-lock. */
|
|
71
|
-
async function writeStateAtomic(state) {
|
|
72
|
-
const p = stateAbsPath();
|
|
73
|
-
const dir = path.dirname(p);
|
|
74
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
75
|
-
const release = await acquire(p, { maxWaitMs: LOCK_MAX_WAIT_MS });
|
|
76
|
-
try {
|
|
77
|
-
// Re-read inside the lock so we merge against the very latest
|
|
78
|
-
// on-disk state, not the value the caller observed pre-lock. This
|
|
79
|
-
// is what makes concurrent consume() from 10 children add up to
|
|
80
|
-
// consumed=10 rather than racing each other.
|
|
81
|
-
const latest = readStateSync();
|
|
82
|
-
const merged = state.mergeFn ? state.mergeFn(latest || state.seed) : state.seed;
|
|
83
|
-
const tmp = `${p}.tmp.${process.pid}.${Date.now()}`;
|
|
84
|
-
fs.writeFileSync(tmp, JSON.stringify(merged, null, 2) + '\n', 'utf8');
|
|
85
|
-
await renameWithRetry(tmp, p);
|
|
86
|
-
return merged;
|
|
87
|
-
} finally {
|
|
88
|
-
await release();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Initialize or restart the iteration budget.
|
|
94
|
-
*
|
|
95
|
-
* @param {number} [budget] default 50
|
|
96
|
-
* @returns {Promise<{budget, remaining, consumed, refunded, updatedAt}>}
|
|
97
|
-
*/
|
|
98
|
-
async function reset(budget = DEFAULT_BUDGET) {
|
|
99
|
-
if (!Number.isFinite(budget) || budget < 0) {
|
|
100
|
-
throw new Error(`iteration-budget.reset: budget must be a non-negative finite number, got ${budget}`);
|
|
101
|
-
}
|
|
102
|
-
const b = Math.floor(budget);
|
|
103
|
-
const state = {
|
|
104
|
-
budget: b,
|
|
105
|
-
remaining: b,
|
|
106
|
-
consumed: 0,
|
|
107
|
-
refunded: 0,
|
|
108
|
-
updatedAt: new Date().toISOString(),
|
|
109
|
-
};
|
|
110
|
-
return writeStateAtomic({ seed: state, mergeFn: () => state });
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Consume N units from the remaining budget. Throws
|
|
115
|
-
* IterationBudgetExhaustedError when N would send remaining below zero.
|
|
116
|
-
*
|
|
117
|
-
* @param {number} [amount] default 1
|
|
118
|
-
* @returns {Promise<{budget, remaining, consumed, refunded, updatedAt}>}
|
|
119
|
-
* the new on-disk state after consumption.
|
|
120
|
-
*/
|
|
121
|
-
async function consume(amount = 1) {
|
|
122
|
-
const n = normalizeAmount(amount);
|
|
123
|
-
// Seed for the case when no state exists yet: auto-init to default budget.
|
|
124
|
-
const seed = defaultState();
|
|
125
|
-
return writeStateAtomic({
|
|
126
|
-
seed,
|
|
127
|
-
mergeFn: (current) => {
|
|
128
|
-
const base = current || seed;
|
|
129
|
-
const nextRemaining = base.remaining - n;
|
|
130
|
-
if (nextRemaining < 0) {
|
|
131
|
-
// Throw without writing — atomic: either consume fully or not at all.
|
|
132
|
-
throw new IterationBudgetExhaustedError(n, base);
|
|
133
|
-
}
|
|
134
|
-
return {
|
|
135
|
-
budget: base.budget,
|
|
136
|
-
remaining: nextRemaining,
|
|
137
|
-
consumed: base.consumed + n,
|
|
138
|
-
refunded: base.refunded,
|
|
139
|
-
updatedAt: new Date().toISOString(),
|
|
140
|
-
};
|
|
141
|
-
},
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Refund N units to the remaining budget, capped at `budget`.
|
|
147
|
-
*
|
|
148
|
-
* @param {number} [amount] default 1
|
|
149
|
-
* @returns {Promise<{budget, remaining, consumed, refunded, updatedAt}>}
|
|
150
|
-
*/
|
|
151
|
-
async function refund(amount = 1) {
|
|
152
|
-
const n = normalizeAmount(amount);
|
|
153
|
-
const seed = defaultState();
|
|
154
|
-
return writeStateAtomic({
|
|
155
|
-
seed,
|
|
156
|
-
mergeFn: (current) => {
|
|
157
|
-
const base = current || seed;
|
|
158
|
-
const nextRemaining = Math.min(base.budget, base.remaining + n);
|
|
159
|
-
// Only count the portion that actually landed — if we were already
|
|
160
|
-
// at budget, the refund is a no-op.
|
|
161
|
-
const actuallyRefunded = nextRemaining - base.remaining;
|
|
162
|
-
return {
|
|
163
|
-
budget: base.budget,
|
|
164
|
-
remaining: nextRemaining,
|
|
165
|
-
consumed: base.consumed,
|
|
166
|
-
refunded: base.refunded + actuallyRefunded,
|
|
167
|
-
updatedAt: new Date().toISOString(),
|
|
168
|
-
};
|
|
169
|
-
},
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Return the current on-disk state. Reads without the lock — callers
|
|
175
|
-
* using this for UI display only see a best-effort snapshot; mutating
|
|
176
|
-
* paths (consume/refund) always re-read inside the lock.
|
|
177
|
-
*/
|
|
178
|
-
function remaining() {
|
|
179
|
-
return readStateSync() || defaultState();
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function normalizeAmount(amount) {
|
|
183
|
-
if (!Number.isFinite(amount) || amount <= 0) {
|
|
184
|
-
throw new Error(`iteration-budget: amount must be a positive finite number, got ${amount}`);
|
|
185
|
-
}
|
|
186
|
-
return Math.floor(amount);
|
|
187
|
-
}
|
|
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).
|
|
188
19
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
};
|
|
20
|
+
let warned = false;
|
|
21
|
+
if (!warned) {
|
|
22
|
+
warned = true;
|
|
23
|
+
process.emitWarning(
|
|
24
|
+
'scripts/lib/iteration-budget.cjs is deprecated; import sdk/primitives/iteration-budget instead. Removed in v1.33.0.',
|
|
25
|
+
'DeprecationWarning',
|
|
26
|
+
);
|
|
197
27
|
}
|
|
198
28
|
|
|
199
|
-
module.exports =
|
|
200
|
-
consume,
|
|
201
|
-
refund,
|
|
202
|
-
remaining,
|
|
203
|
-
reset,
|
|
204
|
-
IterationBudgetExhaustedError,
|
|
205
|
-
};
|
|
29
|
+
module.exports = require('../../sdk/primitives/iteration-budget.cjs');
|
|
@@ -1,112 +1,29 @@
|
|
|
1
|
-
|
|
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
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
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
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
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
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 =
|
|
29
|
+
module.exports = require('../../sdk/primitives/jittered-backoff.cjs');
|