@hegemonart/get-design-done 1.59.6 → 1.59.8
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 +55 -0
- package/README.md +4 -13
- package/SKILL.md +1 -1
- package/agents/design-authority-watcher.md +24 -5
- package/bin/gdd-graph +4 -1
- package/docs/i18n/README.de.md +210 -527
- package/docs/i18n/README.fr.md +201 -518
- package/docs/i18n/README.it.md +209 -526
- package/docs/i18n/README.ja.md +207 -524
- package/docs/i18n/README.ko.md +208 -525
- package/docs/i18n/README.zh-CN.md +213 -551
- package/hooks/_hook-emit.js +113 -29
- package/hooks/budget-enforcer.ts +44 -5
- package/hooks/gdd-mcp-circuit-breaker.js +72 -3
- package/hooks/gdd-sessionstart-recap.js +23 -14
- package/hooks/hooks.json +2 -2
- package/package.json +2 -2
- package/reference/bandit-integration.md +13 -2
- package/scripts/bootstrap.cjs +40 -8
- package/scripts/install.cjs +23 -1
- package/scripts/lib/bandit-router.cjs +47 -5
- package/scripts/lib/detect/cli.cjs +13 -3
- package/scripts/lib/install/converters/cursor.cjs +11 -19
- package/scripts/lib/install/doctor-codex-plugin.cjs +1 -1
- package/scripts/lib/install/doctor-cursor-marketplace.cjs +2 -2
- package/scripts/lib/install/installer.cjs +72 -21
- package/scripts/lib/install/merge.cjs +31 -3
- package/scripts/lib/install/runtime-artifact-layout.cjs +42 -8
- package/scripts/lib/manifest/harnesses.json +29 -1
- package/scripts/lib/manifest/skills.json +1 -1
- package/scripts/skill-templates/bandit-reset/SKILL.md +2 -0
- package/scripts/skill-templates/bandit-status/SKILL.md +4 -1
- package/scripts/skill-templates/darkmode/SKILL.md +1 -1
- package/scripts/skill-templates/graphify/SKILL.md +6 -6
- package/scripts/skill-templates/quick/SKILL.md +3 -1
- package/scripts/skill-templates/reflect/SKILL.md +1 -1
- package/scripts/skill-templates/router/SKILL.md +4 -2
- package/sdk/cli/index.js +114 -47
- package/sdk/dashboard/data/source.cjs +50 -4
- package/sdk/event-stream/writer.ts +112 -30
- package/sdk/mcp/gdd-mcp/server.js +49 -36
- package/sdk/mcp/gdd-mcp/tools/shared.ts +20 -2
- package/sdk/mcp/gdd-state/server.js +107 -41
- package/sdk/primitives/lockfile.cjs +26 -5
- package/sdk/state/index.ts +91 -17
- package/sdk/state/lockfile.ts +47 -8
- package/skills/bandit-reset/SKILL.md +2 -0
- package/skills/bandit-status/SKILL.md +4 -1
- package/skills/darkmode/SKILL.md +1 -1
- package/skills/graphify/SKILL.md +6 -6
- package/skills/quick/SKILL.md +3 -1
- package/skills/reflect/SKILL.md +1 -1
- package/skills/router/SKILL.md +4 -2
|
@@ -2034,7 +2034,7 @@ __export(server_exports, {
|
|
|
2034
2034
|
});
|
|
2035
2035
|
module.exports = __toCommonJS(server_exports);
|
|
2036
2036
|
var import_node_fs5 = require("node:fs");
|
|
2037
|
-
var
|
|
2037
|
+
var import_node_path5 = require("node:path");
|
|
2038
2038
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
2039
2039
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
2040
2040
|
var import_types6 = require("@modelcontextprotocol/sdk/types.js");
|
|
@@ -2199,6 +2199,11 @@ function resolveProjectRoot(startCwd = process.cwd()) {
|
|
|
2199
2199
|
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, ".design")) || (0, import_node_fs.existsSync)((0, import_node_path.join)(dir, ".planning")) || (0, import_node_fs.existsSync)((0, import_node_path.join)(dir, ".claude-plugin", "plugin.json"))) {
|
|
2200
2200
|
return dir;
|
|
2201
2201
|
}
|
|
2202
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, ".git"))) {
|
|
2203
|
+
throw new Error(
|
|
2204
|
+
`gdd project root not found: hit repo boundary at ${dir} (.git) before any GDD marker, starting from ${startCwd}`
|
|
2205
|
+
);
|
|
2206
|
+
}
|
|
2202
2207
|
const parent = (0, import_node_path.dirname)(dir);
|
|
2203
2208
|
if (parent === dir) {
|
|
2204
2209
|
throw new Error(
|
|
@@ -2249,6 +2254,8 @@ __export(gdd_cycle_recap_exports, {
|
|
|
2249
2254
|
|
|
2250
2255
|
// sdk/state/index.ts
|
|
2251
2256
|
var import_node_fs2 = require("node:fs");
|
|
2257
|
+
var import_node_path2 = require("node:path");
|
|
2258
|
+
var import_node_module = require("node:module");
|
|
2252
2259
|
|
|
2253
2260
|
// sdk/state/types.ts
|
|
2254
2261
|
function isConnectionStatus(value) {
|
|
@@ -2950,6 +2957,8 @@ var GATES = Object.freeze({
|
|
|
2950
2957
|
});
|
|
2951
2958
|
|
|
2952
2959
|
// sdk/state/index.ts
|
|
2960
|
+
var _moduleDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path2.dirname)(process.argv[1] || process.cwd());
|
|
2961
|
+
var _require = typeof require !== "undefined" ? require : (0, import_node_module.createRequire)(process.argv[1] || process.cwd());
|
|
2953
2962
|
async function read(path) {
|
|
2954
2963
|
const raw = (0, import_node_fs2.readFileSync)(path, "utf8");
|
|
2955
2964
|
return parse(raw).state;
|
|
@@ -3017,51 +3026,55 @@ __export(gdd_events_tail_exports, {
|
|
|
3017
3026
|
|
|
3018
3027
|
// sdk/event-stream/writer.ts
|
|
3019
3028
|
var import_node_fs3 = require("node:fs");
|
|
3020
|
-
var
|
|
3021
|
-
var
|
|
3029
|
+
var import_node_path3 = require("node:path");
|
|
3030
|
+
var import_node_module2 = require("node:module");
|
|
3022
3031
|
function _findRepoRoot() {
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3032
|
+
return _walkToPackageJson(process.cwd());
|
|
3033
|
+
}
|
|
3034
|
+
function _walkToPackageJson(startDir) {
|
|
3035
|
+
let dir = startDir;
|
|
3036
|
+
for (let i = 0; i < 12; i++) {
|
|
3037
|
+
if ((0, import_node_fs3.existsSync)((0, import_node_path3.join)(dir, "package.json"))) return dir;
|
|
3038
|
+
const parent = (0, import_node_path3.dirname)(dir);
|
|
3027
3039
|
if (parent === dir) break;
|
|
3028
3040
|
dir = parent;
|
|
3029
3041
|
}
|
|
3030
|
-
return
|
|
3042
|
+
return startDir;
|
|
3031
3043
|
}
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
const
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
const
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3044
|
+
function _loadRedact() {
|
|
3045
|
+
const candidates = [];
|
|
3046
|
+
const entry = process.argv[1];
|
|
3047
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
3048
|
+
const entryAbs = (0, import_node_path3.isAbsolute)(entry) ? entry : (0, import_node_path3.resolve)(entry);
|
|
3049
|
+
const entryRoot = _walkToPackageJson((0, import_node_path3.dirname)(entryAbs));
|
|
3050
|
+
candidates.push((0, import_node_path3.resolve)(entryRoot, "scripts/lib/redact.cjs"));
|
|
3051
|
+
}
|
|
3052
|
+
const repoRoot = _findRepoRoot();
|
|
3053
|
+
candidates.push((0, import_node_path3.resolve)(repoRoot, "scripts/lib/redact.cjs"));
|
|
3054
|
+
candidates.push((0, import_node_path3.resolve)(repoRoot, "..", "..", "scripts/lib/redact.cjs"));
|
|
3055
|
+
for (const candidate of candidates) {
|
|
3056
|
+
try {
|
|
3057
|
+
if (!(0, import_node_fs3.existsSync)(candidate)) continue;
|
|
3058
|
+
const req = (0, import_node_module2.createRequire)(candidate);
|
|
3059
|
+
const mod = req(candidate);
|
|
3060
|
+
if (mod && typeof mod.redact === "function") return mod.redact;
|
|
3061
|
+
} catch {
|
|
3049
3062
|
}
|
|
3050
3063
|
}
|
|
3051
|
-
|
|
3052
|
-
_redact = (v) => v;
|
|
3064
|
+
return null;
|
|
3053
3065
|
}
|
|
3066
|
+
var _realRedact = _loadRedact();
|
|
3054
3067
|
var DEFAULT_EVENTS_PATH = ".design/telemetry/events.jsonl";
|
|
3055
3068
|
var DEFAULT_MAX_LINE_BYTES = 64 * 1024;
|
|
3056
3069
|
|
|
3057
3070
|
// sdk/event-stream/reader.ts
|
|
3058
3071
|
var import_node_fs4 = require("node:fs");
|
|
3059
|
-
var
|
|
3072
|
+
var import_node_path4 = require("node:path");
|
|
3060
3073
|
var import_node_readline = require("node:readline");
|
|
3061
3074
|
function resolveReadPath(opts) {
|
|
3062
3075
|
const raw = opts.path ?? DEFAULT_EVENTS_PATH;
|
|
3063
|
-
if ((0,
|
|
3064
|
-
return (0,
|
|
3076
|
+
if ((0, import_node_path4.isAbsolute)(raw)) return raw;
|
|
3077
|
+
return (0, import_node_path4.resolve)(opts.baseDir ?? process.cwd(), raw);
|
|
3065
3078
|
}
|
|
3066
3079
|
async function* readEvents(opts = {}) {
|
|
3067
3080
|
const path = resolveReadPath(opts);
|
|
@@ -3354,16 +3367,16 @@ if (TOOL_COUNT > 13) {
|
|
|
3354
3367
|
var SERVER_NAME = "gdd-mcp";
|
|
3355
3368
|
var SERVER_VERSION = "1.27.7";
|
|
3356
3369
|
function here() {
|
|
3357
|
-
const expectedRel = (0,
|
|
3370
|
+
const expectedRel = (0, import_node_path5.join)("sdk", "mcp", "gdd-mcp");
|
|
3358
3371
|
const entry = process.argv[1];
|
|
3359
3372
|
if (typeof entry === "string" && entry.length > 0) {
|
|
3360
|
-
const entryDir = (0,
|
|
3361
|
-
if ((0, import_node_fs5.existsSync)((0,
|
|
3373
|
+
const entryDir = (0, import_node_path5.dirname)((0, import_node_path5.resolve)(entry));
|
|
3374
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path5.join)(entryDir, "tools", "index.ts"))) {
|
|
3362
3375
|
return entryDir;
|
|
3363
3376
|
}
|
|
3364
3377
|
}
|
|
3365
|
-
const candidate = (0,
|
|
3366
|
-
if ((0, import_node_fs5.existsSync)((0,
|
|
3378
|
+
const candidate = (0, import_node_path5.resolve)(process.cwd(), expectedRel);
|
|
3379
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path5.join)(candidate, "tools", "index.ts"))) {
|
|
3367
3380
|
return candidate;
|
|
3368
3381
|
}
|
|
3369
3382
|
return candidate;
|
|
@@ -3371,7 +3384,7 @@ function here() {
|
|
|
3371
3384
|
function loadTools() {
|
|
3372
3385
|
const baseDir = here();
|
|
3373
3386
|
return TOOL_MODULES.map((m) => {
|
|
3374
|
-
const absPath = (0,
|
|
3387
|
+
const absPath = (0, import_node_path5.join)(baseDir, "tools", m.schemaPath);
|
|
3375
3388
|
const raw = (0, import_node_fs5.readFileSync)(absPath, "utf8");
|
|
3376
3389
|
const parsed = JSON.parse(raw);
|
|
3377
3390
|
const rawInput = parsed.properties?.input;
|
|
@@ -103,9 +103,18 @@ export function resolveSnapshotsDir(): string {
|
|
|
103
103
|
* is returned verbatim (after path resolution). This is useful for
|
|
104
104
|
* tests and for users who want to pin a project root explicitly.
|
|
105
105
|
*
|
|
106
|
+
* REPO-BOUNDARY GUARD (audit S8): the upward walk STOPS at the first `.git`
|
|
107
|
+
* directory it encounters. If a `.git` boundary is hit BEFORE any GDD marker
|
|
108
|
+
* is found, the walk does NOT continue into the parent repository — that
|
|
109
|
+
* would let a nested, unrelated checkout resolve to a PARENT repo's
|
|
110
|
+
* `.design/`/`.planning/`, leaking another project's state into this one
|
|
111
|
+
* (cross-project info bleed). At a `.git` boundary we check the boundary
|
|
112
|
+
* directory itself for a marker (a repo root legitimately holds `.design/`),
|
|
113
|
+
* then treat "no marker at or below this repo root" as not-found.
|
|
114
|
+
*
|
|
106
115
|
* Throws `Error('gdd project root not found: ...')` when no marker is
|
|
107
|
-
* found before the
|
|
108
|
-
* catch and forward via `errorResponse()`.
|
|
116
|
+
* found before either the first `.git` boundary or the filesystem root.
|
|
117
|
+
* Callers in tool handlers should catch and forward via `errorResponse()`.
|
|
109
118
|
*/
|
|
110
119
|
export function resolveProjectRoot(startCwd: string = process.cwd()): string {
|
|
111
120
|
const override = process.env['GDD_PROJECT_ROOT'];
|
|
@@ -122,6 +131,15 @@ export function resolveProjectRoot(startCwd: string = process.cwd()): string {
|
|
|
122
131
|
) {
|
|
123
132
|
return dir;
|
|
124
133
|
}
|
|
134
|
+
// S8: a `.git` here marks a repository boundary. We already checked this
|
|
135
|
+
// directory for a marker above and found none, so do not walk PAST the
|
|
136
|
+
// repo root into a parent (possibly unrelated) project.
|
|
137
|
+
if (existsSync(join(dir, '.git'))) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`gdd project root not found: hit repo boundary at ${dir} ` +
|
|
140
|
+
`(.git) before any GDD marker, starting from ${startCwd}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
125
143
|
const parent = dirname(dir);
|
|
126
144
|
if (parent === dir) {
|
|
127
145
|
// Reached filesystem root — give up.
|
|
@@ -197,6 +197,7 @@ __export(get_exports, {
|
|
|
197
197
|
// sdk/state/index.ts
|
|
198
198
|
var import_node_fs2 = require("node:fs");
|
|
199
199
|
var import_node_path = require("node:path");
|
|
200
|
+
var import_node_module = require("node:module");
|
|
200
201
|
|
|
201
202
|
// sdk/state/lockfile.ts
|
|
202
203
|
var import_node_fs = require("node:fs");
|
|
@@ -263,6 +264,14 @@ async function acquire(path2, opts = {}) {
|
|
|
263
264
|
}
|
|
264
265
|
const parsed = parseLock(existing);
|
|
265
266
|
if (parsed !== null && isStale(parsed, staleMs)) {
|
|
267
|
+
const confirm = readLockSafe(lockPath);
|
|
268
|
+
if (confirm === null) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (confirm !== existing) {
|
|
272
|
+
await sleep(pollMs);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
266
275
|
try {
|
|
267
276
|
(0, import_node_fs.unlinkSync)(lockPath);
|
|
268
277
|
} catch (delErr) {
|
|
@@ -321,10 +330,14 @@ function parseLock(raw) {
|
|
|
321
330
|
}
|
|
322
331
|
}
|
|
323
332
|
function isStale(payload, staleMs) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
333
|
+
const pidRecorded = typeof payload.pid === "number" && Number.isInteger(payload.pid) && payload.pid > 0;
|
|
334
|
+
if (!pidRecorded) {
|
|
335
|
+
const acquiredAt = Date.parse(payload.acquired_at);
|
|
336
|
+
if (!Number.isFinite(acquiredAt)) return true;
|
|
337
|
+
return Date.now() - acquiredAt > staleMs;
|
|
338
|
+
}
|
|
339
|
+
if (isPidAlive(payload.pid, payload.host)) return false;
|
|
340
|
+
return true;
|
|
328
341
|
}
|
|
329
342
|
function isPidAlive(pid, host) {
|
|
330
343
|
if (host !== (0, import_node_os.hostname)()) {
|
|
@@ -1731,6 +1744,8 @@ function gateFor(from, to) {
|
|
|
1731
1744
|
}
|
|
1732
1745
|
|
|
1733
1746
|
// sdk/state/index.ts
|
|
1747
|
+
var _moduleDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path.dirname)(process.argv[1] || process.cwd());
|
|
1748
|
+
var _require = typeof require !== "undefined" ? require : (0, import_node_module.createRequire)(process.argv[1] || process.cwd());
|
|
1734
1749
|
function _findPackageRoot(startDir) {
|
|
1735
1750
|
let dir = (0, import_node_path.resolve)(startDir);
|
|
1736
1751
|
let firstWithPkg = null;
|
|
@@ -1738,7 +1753,7 @@ function _findPackageRoot(startDir) {
|
|
|
1738
1753
|
const pkgPath = (0, import_node_path.join)(dir, "package.json");
|
|
1739
1754
|
if ((0, import_node_fs2.existsSync)(pkgPath)) {
|
|
1740
1755
|
try {
|
|
1741
|
-
const pkg =
|
|
1756
|
+
const pkg = _require(pkgPath);
|
|
1742
1757
|
if (firstWithPkg === null) firstWithPkg = dir;
|
|
1743
1758
|
if (pkg.name === "@hegemonart/get-design-done") return dir;
|
|
1744
1759
|
} catch {
|
|
@@ -1755,7 +1770,7 @@ var _backendCache = null;
|
|
|
1755
1770
|
function _loadBackend() {
|
|
1756
1771
|
if (_backendCache !== null) return _backendCache === false ? null : _backendCache;
|
|
1757
1772
|
try {
|
|
1758
|
-
const pkgRoot = _findPackageRoot(
|
|
1773
|
+
const pkgRoot = _findPackageRoot(_moduleDir);
|
|
1759
1774
|
if (pkgRoot === null) {
|
|
1760
1775
|
_backendCache = false;
|
|
1761
1776
|
return null;
|
|
@@ -1765,7 +1780,7 @@ function _loadBackend() {
|
|
|
1765
1780
|
_backendCache = false;
|
|
1766
1781
|
return null;
|
|
1767
1782
|
}
|
|
1768
|
-
_backendCache =
|
|
1783
|
+
_backendCache = _require(backendPath);
|
|
1769
1784
|
return _backendCache;
|
|
1770
1785
|
} catch {
|
|
1771
1786
|
_backendCache = false;
|
|
@@ -1890,20 +1905,47 @@ async function transition(path2, toStage) {
|
|
|
1890
1905
|
throw new TransitionGateFailed(toStage, gateResult.blockers);
|
|
1891
1906
|
}
|
|
1892
1907
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1908
|
+
let lockedFailure = null;
|
|
1909
|
+
let lockedBlockers = gateResult.blockers;
|
|
1910
|
+
try {
|
|
1911
|
+
const nextState = await mutate(path2, (s) => {
|
|
1912
|
+
const fromNow = s.position.stage;
|
|
1913
|
+
if (!isStage(fromNow)) {
|
|
1914
|
+
lockedFailure = new TransitionGateFailed(toStage, [
|
|
1915
|
+
`Invalid transition: from="${fromNow}" is not a recognized Stage (changed under lock)`
|
|
1916
|
+
]);
|
|
1917
|
+
throw lockedFailure;
|
|
1918
|
+
}
|
|
1919
|
+
const gateNow = gateFor(fromNow, toStage);
|
|
1920
|
+
if (gateNow === null) {
|
|
1921
|
+
lockedFailure = new TransitionGateFailed(toStage, [
|
|
1922
|
+
`Invalid transition: ${fromNow} \u2192 ${toStage} (changed under lock)`
|
|
1923
|
+
]);
|
|
1924
|
+
throw lockedFailure;
|
|
1925
|
+
}
|
|
1926
|
+
const resultNow = gateNow(s);
|
|
1927
|
+
if (!resultNow.pass) {
|
|
1928
|
+
lockedFailure = new TransitionGateFailed(toStage, resultNow.blockers);
|
|
1929
|
+
throw lockedFailure;
|
|
1930
|
+
}
|
|
1931
|
+
lockedBlockers = resultNow.blockers;
|
|
1932
|
+
s.frontmatter.stage = toStage;
|
|
1933
|
+
s.frontmatter.last_checkpoint = nowIso;
|
|
1934
|
+
s.position.stage = toStage;
|
|
1935
|
+
s.timestamps[`${toStage}_started_at`] = nowIso;
|
|
1936
|
+
return s;
|
|
1937
|
+
});
|
|
1938
|
+
return { pass: true, blockers: lockedBlockers, state: nextState };
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
if (lockedFailure !== null && err === lockedFailure) throw lockedFailure;
|
|
1941
|
+
throw err;
|
|
1942
|
+
}
|
|
1901
1943
|
}
|
|
1902
1944
|
|
|
1903
1945
|
// sdk/mcp/gdd-state/tools/shared.ts
|
|
1904
1946
|
var import_node_path3 = __toESM(require("node:path"));
|
|
1905
1947
|
var import_node_fs4 = require("node:fs");
|
|
1906
|
-
var
|
|
1948
|
+
var import_node_module3 = require("node:module");
|
|
1907
1949
|
|
|
1908
1950
|
// sdk/event-stream/index.ts
|
|
1909
1951
|
var import_node_os2 = require("node:os");
|
|
@@ -1952,40 +1994,64 @@ var EventBus = class extends import_node_events.EventEmitter {
|
|
|
1952
1994
|
// sdk/event-stream/writer.ts
|
|
1953
1995
|
var import_node_fs3 = require("node:fs");
|
|
1954
1996
|
var import_node_path2 = require("node:path");
|
|
1955
|
-
var
|
|
1997
|
+
var import_node_module2 = require("node:module");
|
|
1956
1998
|
function _findRepoRoot() {
|
|
1957
|
-
|
|
1958
|
-
|
|
1999
|
+
return _walkToPackageJson(process.cwd());
|
|
2000
|
+
}
|
|
2001
|
+
function _walkToPackageJson(startDir) {
|
|
2002
|
+
let dir = startDir;
|
|
2003
|
+
for (let i = 0; i < 12; i++) {
|
|
1959
2004
|
if ((0, import_node_fs3.existsSync)((0, import_node_path2.join)(dir, "package.json"))) return dir;
|
|
1960
2005
|
const parent = (0, import_node_path2.dirname)(dir);
|
|
1961
2006
|
if (parent === dir) break;
|
|
1962
2007
|
dir = parent;
|
|
1963
2008
|
}
|
|
1964
|
-
return
|
|
2009
|
+
return startDir;
|
|
1965
2010
|
}
|
|
1966
|
-
var
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
}
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
2011
|
+
var _redactWarned = false;
|
|
2012
|
+
function _warnRedactUnavailable() {
|
|
2013
|
+
if (_redactWarned) return;
|
|
2014
|
+
_redactWarned = true;
|
|
2015
|
+
try {
|
|
2016
|
+
process.stderr.write(
|
|
2017
|
+
"[event-stream] WARNING: scripts/lib/redact.cjs could not be loaded \u2014 failing CLOSED: event payloads are dropped (envelope-only) to avoid writing unscrubbed secrets. Run the event writer from inside the plugin tree or set the redact lib on PATH to restore full payloads.\n"
|
|
2018
|
+
);
|
|
2019
|
+
} catch {
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
function _loadRedact() {
|
|
2023
|
+
const candidates = [];
|
|
2024
|
+
const entry = process.argv[1];
|
|
2025
|
+
if (typeof entry === "string" && entry.length > 0) {
|
|
2026
|
+
const entryAbs = (0, import_node_path2.isAbsolute)(entry) ? entry : (0, import_node_path2.resolve)(entry);
|
|
2027
|
+
const entryRoot = _walkToPackageJson((0, import_node_path2.dirname)(entryAbs));
|
|
2028
|
+
candidates.push((0, import_node_path2.resolve)(entryRoot, "scripts/lib/redact.cjs"));
|
|
2029
|
+
}
|
|
2030
|
+
const repoRoot = _findRepoRoot();
|
|
2031
|
+
candidates.push((0, import_node_path2.resolve)(repoRoot, "scripts/lib/redact.cjs"));
|
|
2032
|
+
candidates.push((0, import_node_path2.resolve)(repoRoot, "..", "..", "scripts/lib/redact.cjs"));
|
|
2033
|
+
for (const candidate of candidates) {
|
|
2034
|
+
try {
|
|
2035
|
+
if (!(0, import_node_fs3.existsSync)(candidate)) continue;
|
|
2036
|
+
const req = (0, import_node_module2.createRequire)(candidate);
|
|
2037
|
+
const mod = req(candidate);
|
|
2038
|
+
if (mod && typeof mod.redact === "function") return mod.redact;
|
|
2039
|
+
} catch {
|
|
1983
2040
|
}
|
|
1984
2041
|
}
|
|
1985
|
-
|
|
1986
|
-
_redact = (v) => v;
|
|
2042
|
+
return null;
|
|
1987
2043
|
}
|
|
1988
|
-
var
|
|
2044
|
+
var _realRedact = _loadRedact();
|
|
2045
|
+
var redact = _realRedact !== null ? _realRedact : (v) => {
|
|
2046
|
+
_warnRedactUnavailable();
|
|
2047
|
+
if (v !== null && typeof v === "object") {
|
|
2048
|
+
const ev = v;
|
|
2049
|
+
const out = { ...ev };
|
|
2050
|
+
out["payload"] = { _redaction_unavailable: true };
|
|
2051
|
+
return out;
|
|
2052
|
+
}
|
|
2053
|
+
return { _redaction_unavailable: true };
|
|
2054
|
+
};
|
|
1989
2055
|
var DEFAULT_EVENTS_PATH = ".design/telemetry/events.jsonl";
|
|
1990
2056
|
var DEFAULT_MAX_LINE_BYTES = 64 * 1024;
|
|
1991
2057
|
var EventWriter = class {
|
|
@@ -2132,7 +2198,7 @@ var _worktree = (() => {
|
|
|
2132
2198
|
const root = _findRepoRoot2();
|
|
2133
2199
|
const candidate = import_node_path3.default.resolve(root, "scripts/lib/worktree-resolve.cjs");
|
|
2134
2200
|
if (!(0, import_node_fs4.existsSync)(candidate)) return null;
|
|
2135
|
-
const req = (0,
|
|
2201
|
+
const req = (0, import_node_module3.createRequire)(import_node_path3.default.join(root, "package.json"));
|
|
2136
2202
|
return req(candidate);
|
|
2137
2203
|
} catch {
|
|
2138
2204
|
return null;
|
|
@@ -82,8 +82,16 @@ async function acquire(path, opts) {
|
|
|
82
82
|
// reads (EACCES/EPERM/EBUSY), and clearing under that condition
|
|
83
83
|
// would let two writers race and lose increments.
|
|
84
84
|
if (parsed !== null && isStale(parsed, staleMs)) {
|
|
85
|
+
// Audit D3 (TOCTOU): confirm the on-disk bytes STILL match the exact
|
|
86
|
+
// stale payload we just read before unlinking. If a different writer
|
|
87
|
+
// replaced the lock in the read→unlink window, do NOT unlink (that
|
|
88
|
+
// would steal a fresh lock); loop and re-evaluate the new holder.
|
|
89
|
+
const confirm = readLockSafe(lockPath);
|
|
90
|
+
if (confirm === null) continue; // already gone — race for wx-create
|
|
91
|
+
if (confirm !== existing) { await sleep(pollMs); continue; }
|
|
85
92
|
// Clear stale lock; race-tolerant — if it's already gone we get
|
|
86
|
-
// ENOENT, no-op.
|
|
93
|
+
// ENOENT, no-op. The wx-create below is atomic (O_CREAT|O_EXCL), so
|
|
94
|
+
// even if two waiters both unlink, only one wins the recreate.
|
|
87
95
|
try { fs.unlinkSync(lockPath); } catch { /* ignore */ }
|
|
88
96
|
continue;
|
|
89
97
|
}
|
|
@@ -155,10 +163,23 @@ function parseLock(raw) {
|
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
function isStale(payload, staleMs) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
// Audit D3: PID-liveness is AUTHORITATIVE. A lock whose holder PID is still
|
|
167
|
+
// alive on this host is NEVER stale, regardless of age — a legitimate
|
|
168
|
+
// long-running mutation must not have its lock stolen. The age-based
|
|
169
|
+
// fallback applies ONLY when liveness cannot be confirmed: a dead PID, or a
|
|
170
|
+
// missing/invalid pid field. (isPidAlive conservatively reports alive for
|
|
171
|
+
// cross-host and unsignalable holders, so those are also never aged out.)
|
|
172
|
+
const pidRecorded =
|
|
173
|
+
typeof payload.pid === 'number' &&
|
|
174
|
+
Number.isInteger(payload.pid) &&
|
|
175
|
+
payload.pid > 0;
|
|
176
|
+
if (!pidRecorded) {
|
|
177
|
+
const t = Date.parse(payload.acquired_at);
|
|
178
|
+
if (!Number.isFinite(t)) return true;
|
|
179
|
+
return Date.now() - t > staleMs;
|
|
180
|
+
}
|
|
181
|
+
if (isPidAlive(payload.pid, payload.host)) return false;
|
|
182
|
+
return true;
|
|
162
183
|
}
|
|
163
184
|
|
|
164
185
|
function isPidAlive(pid, host) {
|
package/sdk/state/index.ts
CHANGED
|
@@ -37,6 +37,33 @@ import {
|
|
|
37
37
|
} from 'node:fs';
|
|
38
38
|
import { dirname, join, resolve } from 'node:path';
|
|
39
39
|
import { pathToFileURL } from 'node:url';
|
|
40
|
+
import { createRequire } from 'node:module';
|
|
41
|
+
|
|
42
|
+
// Audit D1: this .ts compiles to CommonJS via tsc (Node16 module mode), where
|
|
43
|
+
// `import.meta` is FORBIDDEN (error TS1470). But under Node's
|
|
44
|
+
// --experimental-strip-types the same file runs as ESM, where the CommonJS
|
|
45
|
+
// globals `require` and `__dirname` are UNDEFINED -- a bare reference to either
|
|
46
|
+
// THROWS a ReferenceError, so we cannot name them directly. We satisfy BOTH
|
|
47
|
+
// targets by probing with `typeof` (safe in ESM) and falling back to the entry
|
|
48
|
+
// script (`process.argv[1]`) when the CJS globals are absent. This mirrors the
|
|
49
|
+
// process.argv[1] anchoring used by sibling `sdk/event-stream/writer.ts` and
|
|
50
|
+
// avoids `import.meta` entirely.
|
|
51
|
+
// * `_require` -- a CJS-style require, used to load the optional .cjs backend
|
|
52
|
+
// (state-backend.cjs) and package.json files. In compiled CJS output the
|
|
53
|
+
// real `require` is used; under strip-types ESM we synthesize one anchored
|
|
54
|
+
// on the entry script via createRequire.
|
|
55
|
+
// * `_moduleDir` -- the walk-up anchor formerly spelled `__dirname`. In
|
|
56
|
+
// compiled CJS `__dirname` is used; under ESM we derive a directory from
|
|
57
|
+
// the entry script. Either way `_loadBackend` can resolve the optional
|
|
58
|
+
// native backend in BOTH compiled and source modes.
|
|
59
|
+
const _moduleDir: string =
|
|
60
|
+
typeof __dirname !== 'undefined'
|
|
61
|
+
? __dirname
|
|
62
|
+
: dirname(process.argv[1] || process.cwd());
|
|
63
|
+
const _require =
|
|
64
|
+
typeof require !== 'undefined'
|
|
65
|
+
? require
|
|
66
|
+
: createRequire(process.argv[1] || process.cwd());
|
|
40
67
|
|
|
41
68
|
import { acquire, acquireSqliteLock } from './lockfile.ts';
|
|
42
69
|
import { parse } from './parser.ts';
|
|
@@ -69,8 +96,8 @@ function _findPackageRoot(startDir: string): string | null {
|
|
|
69
96
|
const pkgPath = join(dir, 'package.json');
|
|
70
97
|
if (existsSync(pkgPath)) {
|
|
71
98
|
try {
|
|
72
|
-
//
|
|
73
|
-
const pkg =
|
|
99
|
+
// D1: createRequire-bound require (bare `require` is undefined in ESM).
|
|
100
|
+
const pkg = _require(pkgPath) as { name?: string };
|
|
74
101
|
if (firstWithPkg === null) firstWithPkg = dir;
|
|
75
102
|
if (pkg.name === '@hegemonart/get-design-done') return dir;
|
|
76
103
|
} catch {
|
|
@@ -85,9 +112,16 @@ function _findPackageRoot(startDir: string): string | null {
|
|
|
85
112
|
}
|
|
86
113
|
|
|
87
114
|
// ---------------------------------------------------------------------------
|
|
88
|
-
// Phase 57: backend probe (loaded once via
|
|
89
|
-
// state-backend.cjs is a CommonJS module
|
|
90
|
-
// Node
|
|
115
|
+
// Phase 57: backend probe (loaded once via createRequire, memoized).
|
|
116
|
+
// state-backend.cjs is a CommonJS module. Audit D1 correction: a BARE
|
|
117
|
+
// `require()` does NOT work from this .ts under Node's --experimental-strip-
|
|
118
|
+
// types — this module is loaded as ESM, where `require` is undefined and a
|
|
119
|
+
// bare call throws ReferenceError (silently caught below, killing the backend
|
|
120
|
+
// path). We load via `_require` -- the real CJS `require` in compiled output,
|
|
121
|
+
// or a createRequire anchored on the entry script under strip-types ESM --
|
|
122
|
+
// which DOES resolve the optional .cjs backend in both modes. The graceful-null
|
|
123
|
+
// fallback is preserved for the genuinely-absent
|
|
124
|
+
// dependency (e.g. better-sqlite3 not installed).
|
|
91
125
|
// ---------------------------------------------------------------------------
|
|
92
126
|
|
|
93
127
|
interface StateBackendMod {
|
|
@@ -111,12 +145,13 @@ let _backendCache: StateBackendMod | null | false = null;
|
|
|
111
145
|
function _loadBackend(): StateBackendMod | null {
|
|
112
146
|
if (_backendCache !== null) return _backendCache === false ? null : _backendCache as StateBackendMod;
|
|
113
147
|
try {
|
|
114
|
-
const pkgRoot = _findPackageRoot(
|
|
148
|
+
const pkgRoot = _findPackageRoot(_moduleDir);
|
|
115
149
|
if (pkgRoot === null) { _backendCache = false; return null; }
|
|
116
150
|
const backendPath = join(pkgRoot, 'scripts', 'lib', 'state', 'state-backend.cjs');
|
|
117
151
|
if (!existsSync(backendPath)) { _backendCache = false; return null; }
|
|
118
|
-
//
|
|
119
|
-
|
|
152
|
+
// D1: createRequire-bound require so the optional native backend can
|
|
153
|
+
// actually load from this ESM (.ts strip-types) context.
|
|
154
|
+
_backendCache = _require(backendPath) as StateBackendMod;
|
|
120
155
|
return _backendCache as StateBackendMod;
|
|
121
156
|
} catch {
|
|
122
157
|
_backendCache = false;
|
|
@@ -144,7 +179,7 @@ let _storeCache: StateStoreMod | null | false = null;
|
|
|
144
179
|
async function _loadStore(): Promise<StateStoreMod | null> {
|
|
145
180
|
if (_storeCache !== null) return _storeCache === false ? null : _storeCache as StateStoreMod;
|
|
146
181
|
try {
|
|
147
|
-
const pkgRoot = _findPackageRoot(
|
|
182
|
+
const pkgRoot = _findPackageRoot(_moduleDir);
|
|
148
183
|
if (pkgRoot === null) { _storeCache = false; return null; }
|
|
149
184
|
const storePath = join(pkgRoot, 'scripts', 'lib', 'state', 'state-store.cjs');
|
|
150
185
|
if (!existsSync(storePath)) { _storeCache = false; return null; }
|
|
@@ -420,12 +455,51 @@ export async function transition(
|
|
|
420
455
|
throw new TransitionGateFailed(toStage, gateResult.blockers);
|
|
421
456
|
}
|
|
422
457
|
const nowIso: string = new Date().toISOString();
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
458
|
+
// Audit D4: the gate above was evaluated against a PRE-LOCK read. A
|
|
459
|
+
// concurrent stage change between that read and the locked mutate could make
|
|
460
|
+
// the transition invalid (e.g. another writer already advanced the stage, so
|
|
461
|
+
// `from` is no longer the current stage, or the gate's preconditions no
|
|
462
|
+
// longer hold). Re-evaluate the gate INSIDE the locked mutate against the
|
|
463
|
+
// freshly-read `s`, and abort the transition if it no longer holds. The
|
|
464
|
+
// mutate() lock serializes us against other writers, so this re-check is
|
|
465
|
+
// race-free: nothing can change `s` between this check and the write.
|
|
466
|
+
//
|
|
467
|
+
// We capture the locked re-check failure and re-throw it OUTSIDE mutate so
|
|
468
|
+
// the caller sees a TransitionGateFailed rather than a generic mutate error.
|
|
469
|
+
let lockedFailure: TransitionGateFailed | null = null;
|
|
470
|
+
let lockedBlockers: string[] = gateResult.blockers;
|
|
471
|
+
try {
|
|
472
|
+
const nextState = await mutate(path, (s): ParsedState => {
|
|
473
|
+
const fromNow: string = s.position.stage;
|
|
474
|
+
if (!isStage(fromNow)) {
|
|
475
|
+
lockedFailure = new TransitionGateFailed(toStage, [
|
|
476
|
+
`Invalid transition: from="${fromNow}" is not a recognized Stage (changed under lock)`,
|
|
477
|
+
]);
|
|
478
|
+
throw lockedFailure;
|
|
479
|
+
}
|
|
480
|
+
const gateNow = gateFor(fromNow, toStage);
|
|
481
|
+
if (gateNow === null) {
|
|
482
|
+
lockedFailure = new TransitionGateFailed(toStage, [
|
|
483
|
+
`Invalid transition: ${fromNow} → ${toStage} (changed under lock)`,
|
|
484
|
+
]);
|
|
485
|
+
throw lockedFailure;
|
|
486
|
+
}
|
|
487
|
+
const resultNow = gateNow(s);
|
|
488
|
+
if (!resultNow.pass) {
|
|
489
|
+
lockedFailure = new TransitionGateFailed(toStage, resultNow.blockers);
|
|
490
|
+
throw lockedFailure;
|
|
491
|
+
}
|
|
492
|
+
lockedBlockers = resultNow.blockers;
|
|
493
|
+
s.frontmatter.stage = toStage;
|
|
494
|
+
s.frontmatter.last_checkpoint = nowIso;
|
|
495
|
+
s.position.stage = toStage;
|
|
496
|
+
s.timestamps[`${toStage}_started_at`] = nowIso;
|
|
497
|
+
return s;
|
|
498
|
+
});
|
|
499
|
+
return { pass: true, blockers: lockedBlockers, state: nextState };
|
|
500
|
+
} catch (err) {
|
|
501
|
+
// If the in-lock re-check vetoed, surface the gate failure verbatim.
|
|
502
|
+
if (lockedFailure !== null && err === lockedFailure) throw lockedFailure;
|
|
503
|
+
throw err;
|
|
504
|
+
}
|
|
431
505
|
}
|