@hegemonart/get-design-done 1.59.7 → 1.59.9
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 +59 -0
- package/README.md +2 -2
- package/SKILL.md +1 -1
- package/agents/design-authority-watcher.md +24 -5
- package/bin/gdd-graph +4 -1
- package/hooks/_hook-emit.js +113 -29
- package/hooks/budget-enforcer.ts +104 -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/reference/prices/claude.md +11 -0
- package/reference/runtime-models.md +9 -9
- package/reference/schemas/generated.d.ts +4 -0
- package/reference/schemas/runtime-models.schema.json +5 -0
- package/scripts/bootstrap.cjs +40 -8
- package/scripts/install.cjs +23 -1
- package/scripts/lib/bandit-router.cjs +47 -5
- package/scripts/lib/budget-enforcer.cjs +34 -5
- package/scripts/lib/detect/cli.cjs +13 -3
- package/scripts/lib/install/converters/cursor.cjs +11 -19
- package/scripts/lib/install/installer.cjs +72 -21
- package/scripts/lib/install/merge.cjs +31 -3
- package/scripts/lib/install/parse-runtime-models.cjs +9 -1
- 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/lib/model-id.cjs +141 -0
- package/scripts/lib/session-runner/index.ts +87 -16
- 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 +132 -55
- 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
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
* `degraded[]` (so gsd-health + the TUI can surface what is missing).
|
|
25
25
|
* - Absent .design entirely -> every data section null/[] + degraded
|
|
26
26
|
* populated, still no throw.
|
|
27
|
-
* - Root resolution: opts.root || GDD_PROJECT_ROOT ||
|
|
28
|
-
* ||
|
|
29
|
-
*
|
|
27
|
+
* - Root resolution: opts.root || GDD_PROJECT_ROOT || cwd marker walk-up
|
|
28
|
+
* (.design/.planning/.claude-plugin) || package-root walk-up. The cwd
|
|
29
|
+
* marker walk resolves the USER's project; package-root is a last-resort
|
|
30
|
+
* fallback (it would otherwise always pin the installed plugin's own dir).
|
|
30
31
|
* - The .ts libs (sdk/state, sdk/event-stream) cannot be static-require()d
|
|
31
32
|
* from a .cjs — they are loaded via dynamic import(pathToFileURL),
|
|
32
33
|
* memoized once per process. The .cjs libs are require()d directly via the
|
|
@@ -100,15 +101,59 @@ function importEventStream() {
|
|
|
100
101
|
// ---------------------------------------------------------------------------
|
|
101
102
|
// Root resolution
|
|
102
103
|
// ---------------------------------------------------------------------------
|
|
104
|
+
/**
|
|
105
|
+
* Walk UP from `startCwd` looking for a GDD project marker — `.design/` OR
|
|
106
|
+
* `.planning/` OR `.claude-plugin/plugin.json`. First match wins; returns the
|
|
107
|
+
* directory that holds the marker, or null if none is found before the
|
|
108
|
+
* filesystem root.
|
|
109
|
+
*
|
|
110
|
+
* This mirrors `resolveProjectRoot()` in
|
|
111
|
+
* `sdk/mcp/gdd-mcp/tools/shared.ts` (the canonical D-05 marker walk) so the
|
|
112
|
+
* dashboard resolves the USER's project, not the plugin's own install dir.
|
|
113
|
+
*
|
|
114
|
+
* @param {string} [startCwd]
|
|
115
|
+
* @returns {string|null}
|
|
116
|
+
*/
|
|
117
|
+
function cwdMarkerWalk(startCwd = process.cwd()) {
|
|
118
|
+
let dir = path.resolve(startCwd);
|
|
119
|
+
// Bound the climb defensively (deep trees / odd mounts).
|
|
120
|
+
for (let i = 0; i < 64; i++) {
|
|
121
|
+
if (
|
|
122
|
+
fs.existsSync(path.join(dir, '.design')) ||
|
|
123
|
+
fs.existsSync(path.join(dir, '.planning')) ||
|
|
124
|
+
fs.existsSync(path.join(dir, '.claude-plugin', 'plugin.json'))
|
|
125
|
+
) {
|
|
126
|
+
return dir;
|
|
127
|
+
}
|
|
128
|
+
const parent = path.dirname(dir);
|
|
129
|
+
if (parent === dir) return null; // reached filesystem root
|
|
130
|
+
dir = parent;
|
|
131
|
+
}
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
|
|
103
135
|
/**
|
|
104
136
|
* Resolve the project root the dashboard reads from:
|
|
105
|
-
* opts.root || GDD_PROJECT_ROOT (env) ||
|
|
137
|
+
* opts.root || GDD_PROJECT_ROOT (env) || cwd marker walk-up || package-root.
|
|
138
|
+
*
|
|
139
|
+
* D2 fix: `packageRoot()` ALWAYS succeeds (it walks up from __dirname and
|
|
140
|
+
* lands on the installed plugin's own package.json, e.g.
|
|
141
|
+
* node_modules/@hegemonart/get-design-done), so when it preceded the cwd
|
|
142
|
+
* resolution an installed `gdd-dashboard` always showed the PLUGIN's own
|
|
143
|
+
* (empty) data instead of the user's project. We now resolve the user's
|
|
144
|
+
* project FIRST via a cwd-upward marker walk (the same D-05 algorithm the
|
|
145
|
+
* gdd-mcp tools use), and only fall back to the package root as a last
|
|
146
|
+
* resort. Running the dashboard INSIDE the gdd repo still works — the marker
|
|
147
|
+
* walk finds the repo's own .design/.planning, which IS the project root.
|
|
148
|
+
*
|
|
106
149
|
* @param {{root?: string}} [opts]
|
|
107
150
|
* @returns {string}
|
|
108
151
|
*/
|
|
109
152
|
function resolveRoot(opts = {}) {
|
|
110
153
|
if (opts.root) return path.resolve(opts.root);
|
|
111
154
|
if (process.env.GDD_PROJECT_ROOT) return path.resolve(process.env.GDD_PROJECT_ROOT);
|
|
155
|
+
const fromCwd = cwdMarkerWalk();
|
|
156
|
+
if (fromCwd) return fromCwd;
|
|
112
157
|
try {
|
|
113
158
|
return packageRoot();
|
|
114
159
|
} catch {
|
|
@@ -610,6 +655,7 @@ module.exports = {
|
|
|
610
655
|
loadDashboardModel,
|
|
611
656
|
// Exposed for tests + sibling reuse (executors D/F may want the scrapers).
|
|
612
657
|
resolveRoot,
|
|
658
|
+
cwdMarkerWalk,
|
|
613
659
|
scrapeStateFile,
|
|
614
660
|
scrapeEventsFile,
|
|
615
661
|
};
|
|
@@ -38,46 +38,128 @@ import type { BaseEvent } from './types.ts';
|
|
|
38
38
|
// anchor createRequire on the repo-root package.json discovered by
|
|
39
39
|
// walking up from `process.cwd()`.
|
|
40
40
|
function _findRepoRoot(): string {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
return _walkToPackageJson(process.cwd());
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Walk up from `startDir` until a directory containing `package.json` is
|
|
46
|
+
* found; returns `startDir` itself if none is found within the bound.
|
|
47
|
+
*/
|
|
48
|
+
function _walkToPackageJson(startDir: string): string {
|
|
49
|
+
let dir = startDir;
|
|
50
|
+
for (let i = 0; i < 12; i++) {
|
|
43
51
|
if (existsSync(join(dir, 'package.json'))) return dir;
|
|
44
52
|
const parent = dirname(dir);
|
|
45
53
|
if (parent === dir) break;
|
|
46
54
|
dir = parent;
|
|
47
55
|
}
|
|
48
|
-
return
|
|
56
|
+
return startDir;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// S2 fix: redaction now fails CLOSED. Previously, if redact.cjs could not be
|
|
60
|
+
// resolved from the runtime cwd, `_redact` fell through to the IDENTITY
|
|
61
|
+
// function and every event was written UNSCRUBBED — silently leaking secrets
|
|
62
|
+
// into events.jsonl whenever the writer ran outside the plugin tree (hook
|
|
63
|
+
// subprocesses, temp test dirs, unusual install layouts). That is a fail-open
|
|
64
|
+
// security hole.
|
|
65
|
+
//
|
|
66
|
+
// New contract:
|
|
67
|
+
// * redact.cjs loads → normal deep-walk scrubbing (unchanged behavior).
|
|
68
|
+
// * redact.cjs MISSING → fail closed: `redact` returns an envelope-only
|
|
69
|
+
// placeholder that DROPS the payload body (replacing it with
|
|
70
|
+
// `{ _redaction_unavailable: true }`) so no raw payload is ever persisted
|
|
71
|
+
// unscrubbed. A single visible stderr warning is emitted (once per
|
|
72
|
+
// process, guarded by `_redactWarned`) so the failure is observable.
|
|
73
|
+
//
|
|
74
|
+
// Resolution is also improved: we try createRequire anchored on THIS module
|
|
75
|
+
// (via the runtime-resolved module path) before the cwd-anchored walk, so it
|
|
76
|
+
// loads in more layouts.
|
|
77
|
+
|
|
78
|
+
/** Module-level guard so the fail-closed warning prints at most once. */
|
|
79
|
+
let _redactWarned = false;
|
|
80
|
+
|
|
81
|
+
/** Emit the one-time fail-closed warning to stderr (guarded, best-effort). */
|
|
82
|
+
function _warnRedactUnavailable(): void {
|
|
83
|
+
if (_redactWarned) return;
|
|
84
|
+
_redactWarned = true;
|
|
85
|
+
try {
|
|
86
|
+
process.stderr.write(
|
|
87
|
+
'[event-stream] WARNING: scripts/lib/redact.cjs could not be loaded — ' +
|
|
88
|
+
'failing CLOSED: event payloads are dropped (envelope-only) to avoid ' +
|
|
89
|
+
'writing unscrubbed secrets. Run the event writer from inside the ' +
|
|
90
|
+
'plugin tree or set the redact lib on PATH to restore full payloads.\n',
|
|
91
|
+
);
|
|
92
|
+
} catch {
|
|
93
|
+
// If stderr itself is broken we have no recourse; swallow.
|
|
94
|
+
}
|
|
49
95
|
}
|
|
50
96
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Attempt to load redact.cjs from a set of candidate roots. Returns the
|
|
99
|
+
* real `redact` function on success, or `null` if no candidate resolves.
|
|
100
|
+
*/
|
|
101
|
+
function _loadRedact(): ((v: unknown) => unknown) | null {
|
|
102
|
+
const candidates: string[] = [];
|
|
103
|
+
|
|
104
|
+
// We cannot use `import.meta.url` (tsc Node16 classifies this .ts as CJS for
|
|
105
|
+
// typecheck), so we probe several anchors so redact.cjs loads in as many
|
|
106
|
+
// layouts as possible BEFORE the fail-closed path engages:
|
|
107
|
+
//
|
|
108
|
+
// 1) The entry script (`process.argv[1]`). For hook subprocesses (e.g.
|
|
109
|
+
// budget-enforcer.ts spawned by the harness) the entry script lives
|
|
110
|
+
// INSIDE the plugin tree even when cwd is a detached temp dir — this is
|
|
111
|
+
// the same anchor the hook itself uses via resolveHookPath(). Walking up
|
|
112
|
+
// from the entry script to its package.json reliably lands on the plugin
|
|
113
|
+
// root regardless of cwd.
|
|
114
|
+
// 2) A cwd-walked repo root (works when cwd IS inside the plugin tree).
|
|
115
|
+
// 3) The source-relative layout (writer.ts → ../../scripts/lib/redact.cjs).
|
|
116
|
+
const entry = process.argv[1];
|
|
117
|
+
if (typeof entry === 'string' && entry.length > 0) {
|
|
118
|
+
const entryAbs = isAbsolute(entry) ? entry : resolve(entry);
|
|
119
|
+
const entryRoot = _walkToPackageJson(dirname(entryAbs));
|
|
120
|
+
candidates.push(resolve(entryRoot, 'scripts/lib/redact.cjs'));
|
|
121
|
+
}
|
|
122
|
+
const repoRoot = _findRepoRoot();
|
|
123
|
+
candidates.push(resolve(repoRoot, 'scripts/lib/redact.cjs'));
|
|
124
|
+
candidates.push(resolve(repoRoot, '..', '..', 'scripts/lib/redact.cjs'));
|
|
125
|
+
|
|
126
|
+
for (const candidate of candidates) {
|
|
127
|
+
try {
|
|
128
|
+
if (!existsSync(candidate)) continue;
|
|
129
|
+
// Anchor createRequire on the candidate file itself so resolution does
|
|
130
|
+
// not depend on a package.json being present at a particular ancestor.
|
|
131
|
+
const req = createRequire(candidate);
|
|
132
|
+
const mod = req(candidate) as { redact?: (v: unknown) => unknown };
|
|
133
|
+
if (mod && typeof mod.redact === 'function') return mod.redact;
|
|
134
|
+
} catch {
|
|
135
|
+
// Try the next candidate.
|
|
75
136
|
}
|
|
76
137
|
}
|
|
77
|
-
|
|
78
|
-
_redact = (v) => v;
|
|
138
|
+
return null;
|
|
79
139
|
}
|
|
80
|
-
|
|
140
|
+
|
|
141
|
+
const _realRedact = _loadRedact();
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The redaction function used at the write boundary. When the real redactor
|
|
145
|
+
* loaded, this is it. When it did not, this is the FAIL-CLOSED shim: it warns
|
|
146
|
+
* once and returns an envelope-only object with the payload body dropped.
|
|
147
|
+
*/
|
|
148
|
+
const redact: (v: unknown) => unknown =
|
|
149
|
+
_realRedact !== null
|
|
150
|
+
? _realRedact
|
|
151
|
+
: (v: unknown): unknown => {
|
|
152
|
+
_warnRedactUnavailable();
|
|
153
|
+
if (v !== null && typeof v === 'object') {
|
|
154
|
+
// Preserve envelope metadata; drop the payload body entirely so no
|
|
155
|
+
// raw (potentially secret-bearing) content is persisted.
|
|
156
|
+
const ev = v as Record<string, unknown>;
|
|
157
|
+
const out: Record<string, unknown> = { ...ev };
|
|
158
|
+
out['payload'] = { _redaction_unavailable: true };
|
|
159
|
+
return out;
|
|
160
|
+
}
|
|
161
|
+
return { _redaction_unavailable: true };
|
|
162
|
+
};
|
|
81
163
|
|
|
82
164
|
/** Default relative path for the persisted event stream. */
|
|
83
165
|
export const DEFAULT_EVENTS_PATH = '.design/telemetry/events.jsonl';
|
|
@@ -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.
|