@hegemonart/get-design-done 1.57.1 → 1.57.2

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 (113) hide show
  1. package/.claude-plugin/marketplace.json +26 -41
  2. package/.claude-plugin/plugin.json +23 -48
  3. package/CHANGELOG.md +91 -0
  4. package/README.md +166 -511
  5. package/SKILL.md +2 -0
  6. package/agents/README.md +33 -36
  7. package/agents/a11y-mapper.md +3 -3
  8. package/agents/component-benchmark-harvester.md +6 -6
  9. package/agents/component-benchmark-synthesizer.md +3 -3
  10. package/agents/compose-executor.md +3 -3
  11. package/agents/cost-forecaster.md +2 -2
  12. package/agents/design-auditor.md +7 -7
  13. package/agents/design-authority-watcher.md +15 -15
  14. package/agents/design-context-builder.md +4 -4
  15. package/agents/design-context-checker-gate.md +1 -1
  16. package/agents/design-discussant.md +2 -2
  17. package/agents/design-doc-writer.md +1 -1
  18. package/agents/design-executor.md +2 -2
  19. package/agents/design-figma-writer.md +2 -2
  20. package/agents/design-fixer.md +7 -7
  21. package/agents/design-integration-checker-gate.md +1 -1
  22. package/agents/design-integration-checker.md +1 -1
  23. package/agents/design-paper-writer.md +3 -3
  24. package/agents/design-pencil-writer.md +1 -1
  25. package/agents/design-planner.md +21 -0
  26. package/agents/design-reflector.md +39 -39
  27. package/agents/design-research-synthesizer.md +1 -0
  28. package/agents/design-start-writer.md +1 -1
  29. package/agents/design-update-checker.md +5 -5
  30. package/agents/design-verifier-gate.md +1 -1
  31. package/agents/design-verifier.md +52 -48
  32. package/agents/ds-generator.md +2 -2
  33. package/agents/ds-migration-planner.md +4 -4
  34. package/agents/email-executor.md +9 -9
  35. package/agents/experiment-result-ingester.md +3 -3
  36. package/agents/flutter-executor.md +5 -5
  37. package/agents/gdd-graph-refresh.md +3 -3
  38. package/agents/gdd-intel-updater.md +2 -2
  39. package/agents/motion-mapper.md +2 -2
  40. package/agents/motion-verifier.md +4 -4
  41. package/agents/pdf-executor.md +8 -8
  42. package/agents/perf-analyzer.md +17 -17
  43. package/agents/pr-commenter.md +9 -9
  44. package/agents/prototype-gate.md +2 -2
  45. package/agents/quality-gate-runner.md +1 -1
  46. package/agents/rollout-coordinator.md +3 -3
  47. package/agents/swift-executor.md +4 -4
  48. package/agents/ticket-sync-agent.md +6 -6
  49. package/agents/user-research-synthesizer.md +2 -2
  50. package/connections/connections.md +44 -45
  51. package/connections/cursor.md +73 -0
  52. package/connections/preview.md +3 -3
  53. package/dist/claude-code/.claude/skills/cache-manager/SKILL.md +3 -3
  54. package/dist/claude-code/.claude/skills/cache-manager/cache-policy.md +1 -1
  55. package/dist/claude-code/.claude/skills/design/SKILL.md +19 -0
  56. package/dist/claude-code/.claude/skills/explore/SKILL.md +11 -0
  57. package/dist/claude-code/.claude/skills/figma-write/SKILL.md +13 -2
  58. package/dist/claude-code/.claude/skills/paper-write/SKILL.md +54 -0
  59. package/dist/claude-code/.claude/skills/pencil-write/SKILL.md +54 -0
  60. package/dist/claude-code/.claude/skills/report-issue/SKILL.md +2 -2
  61. package/dist/claude-code/.claude/skills/router/SKILL.md +2 -2
  62. package/dist/claude-code/.claude/skills/verify/verify-procedure.md +10 -11
  63. package/dist/claude-code/.claude/skills/warm-cache/SKILL.md +1 -1
  64. package/hooks/first-run-nudge.cjs +171 -0
  65. package/hooks/gdd-intel-trigger.js +243 -0
  66. package/hooks/gdd-mcp-circuit-breaker.js +62 -7
  67. package/hooks/gdd-precompact-snapshot.js +50 -29
  68. package/hooks/gdd-protected-paths.js +150 -18
  69. package/hooks/gdd-risk-gate.js +93 -1
  70. package/hooks/gdd-sessionstart-recap.js +59 -24
  71. package/hooks/hooks.json +13 -4
  72. package/hooks/inject-using-gdd.cjs +188 -0
  73. package/hooks/update-check.cjs +511 -0
  74. package/package.json +9 -2
  75. package/reference/STATE-TEMPLATE.md +10 -13
  76. package/reference/audit-scoring.md +1 -1
  77. package/reference/cache-tier-doctrine.md +46 -0
  78. package/reference/config-schema.md +9 -9
  79. package/reference/i18n.md +1 -1
  80. package/reference/intel-schema.md +37 -2
  81. package/reference/meta-rules.md +4 -4
  82. package/reference/model-tiers.md +2 -2
  83. package/reference/registry.json +101 -94
  84. package/reference/runtime-models.md +11 -1
  85. package/reference/shared-preamble.md +13 -14
  86. package/reference/skill-graph.md +24 -1
  87. package/scripts/bootstrap.cjs +373 -0
  88. package/scripts/injection-patterns.cjs +58 -0
  89. package/scripts/lib/apply-reflections/incubator-proposals.cjs +57 -26
  90. package/scripts/lib/install/converters/codex-plugin.cjs +5 -2
  91. package/scripts/lib/install/converters/cursor.cjs +20 -0
  92. package/scripts/lib/issue-reporter/report-flow.cjs +1 -1
  93. package/scripts/lib/manifest/skills.json +80 -13
  94. package/scripts/lib/state/query-surface.cjs +67 -9
  95. package/scripts/lib/state/state-store.cjs +68 -26
  96. package/sdk/cli/commands/stage.ts +17 -0
  97. package/sdk/cli/index.js +14 -0
  98. package/skills/cache-manager/SKILL.md +3 -3
  99. package/skills/cache-manager/cache-policy.md +1 -1
  100. package/skills/design/SKILL.md +19 -0
  101. package/skills/explore/SKILL.md +11 -0
  102. package/skills/figma-write/SKILL.md +13 -2
  103. package/skills/paper-write/SKILL.md +54 -0
  104. package/skills/pencil-write/SKILL.md +54 -0
  105. package/skills/report-issue/SKILL.md +2 -2
  106. package/skills/router/SKILL.md +2 -2
  107. package/skills/verify/verify-procedure.md +10 -11
  108. package/skills/warm-cache/SKILL.md +1 -1
  109. package/hooks/first-run-nudge.sh +0 -82
  110. package/hooks/inject-using-gdd.sh +0 -72
  111. package/hooks/update-check.sh +0 -251
  112. package/scripts/lib/audit-aggregator/index.cjs +0 -219
  113. package/scripts/lib/hedge-ensemble.cjs +0 -217
@@ -1,6 +1,16 @@
1
1
  # Runtime Models - Per-Runtime Tier→Model Adapter Map
2
2
 
3
- **Phase 26 source-of-truth (D-01).** Single canonical map from canonical Anthropic tier names (`opus|sonnet|haiku`) and runtime-neutral reasoning-class aliases (`high|medium|low`, D-10) to concrete model identifiers for each of the 14 runtimes the multi-runtime installer ships to (Phase 24 D-02).
3
+ Single canonical map from Anthropic tier names (`opus|sonnet|haiku`) and runtime-neutral reasoning-class aliases (`high|medium|low`) to concrete model identifiers for each of the 14 runtimes the multi-runtime installer ships to.
4
+
5
+ > ## ⚠️ Verification status
6
+ >
7
+ > 4 of 14 runtime entries below are **verified** against their author docs (claude, codex, gemini, qwen).
8
+ >
9
+ > 10 entries are **unverified** placeholder fills (Anthropic-default mapping where the runtime is BYOK / multi-provider; or single-tier marker when only one model is exposed). Their `source_url` is prefixed with `<TODO: confirm at …>`. Treat these tier names as best-effort; the resolved model may differ for users whose runtime config diverges from the published default.
10
+ >
11
+ > Unverified: kilo, copilot, cursor, windsurf, antigravity, augment, trae, codebuddy, cline, opencode.
12
+ >
13
+ > The schema (`reference/schemas/runtime-models.schema.json`) explicitly accepts the placeholder marker so the file ships shape-valid; the unverified-ness is a content gap, not a structural defect.
4
14
 
5
15
  This file is parsed by `scripts/lib/install/parse-runtime-models.cjs` and consumed by:
6
16
 
@@ -2,18 +2,17 @@
2
2
  name: shared-preamble
3
3
  type: preamble
4
4
  version: 2.0.0
5
- phase: 28.5
6
5
  tags: [shared, preamble, principles, design-quality, agent-import, cache-prefix, extracted]
7
- last_updated: 2026-05-18
6
+ last_updated: 2026-06-03
8
7
  ---
9
8
 
10
- # GSD Agent Shared Preamble
9
+ # GDD Agent Shared Preamble
11
10
 
12
- > **This file is imported via `@reference/shared-preamble.md` as the first line of every agent body in `agents/*.md`. Its placement is essential for Anthropic's 5-minute prompt cache (see `./model-tiers.md` and Phase 10.1 decision D-08 Layer A): because every agent opens with the identical preamble prefix, the second and subsequent agent spawns in a session pay `cached_input_per_1m` rates rather than full `input_per_1m` rates for these bytes. Do not inline this content into agent bodies - always import.**
11
+ > **This file is imported via `@reference/shared-preamble.md` as the first line of every agent body in `agents/*.md`. Its placement is essential for Anthropic's 5-minute prompt cache (see `./model-tiers.md`): because every agent opens with the identical preamble prefix, the second and subsequent agent spawns in a session pay `cached_input_per_1m` rates rather than full `input_per_1m` rates for these bytes. Do not inline this content into agent bodies - always import.**
13
12
  >
14
- > **As of Phase 14.5 this file is an aggregator.** The framework-invariant subsections (Required Reading Discipline, Writes Protocol, Deviation Handling, Completion Markers, Context-Exhaustion & Budget awareness) live in `./meta-rules.md` (tier L0) so the L2 heuristics/anti-patterns/checklists churn never invalidates the L0 prefix.
13
+ > **This file is an aggregator.** The framework-invariant subsections (Required Reading Discipline, Writes Protocol, Deviation Handling, Completion Markers, Context-Exhaustion & Budget awareness) live in `./meta-rules.md` (tier L0) so the L2 heuristics/anti-patterns/checklists churn never invalidates the L0 prefix.
15
14
  >
16
- > **As of Phase 28.5 this file also serves the design-family skills.** Sections below the agent-preamble block (## Design Quality Pillars, ## Token-First Reasoning, ## Output Contract Reminders, ## Connection Handshake Summary) are the canonical home for principle-recitation that previously inlined across `skills/audit`, `skills/style`, `skills/darkmode`, `skills/compare`, `skills/figma-write`, `skills/connections`, `skills/benchmark`. Skills cross-link here instead of restating these lists.
15
+ > **This file also serves the design-family skills.** Sections below the agent-preamble block (## Design Quality Pillars, ## Token-First Reasoning, ## Output Contract Reminders, ## Connection Handshake Summary) are the canonical home for principle-recitation that previously inlined across `skills/audit`, `skills/style`, `skills/darkmode`, `skills/compare`, `skills/figma-write`, `skills/connections`, `skills/benchmark`. Skills cross-link here instead of restating these lists.
17
16
 
18
17
  @reference/meta-rules.md
19
18
 
@@ -21,16 +20,16 @@ last_updated: 2026-05-18
21
20
 
22
21
  Two distinct consumers, one canonical home:
23
22
 
24
- 1. **Agents** (`agents/*.md`) import this file via `@reference/shared-preamble.md` to inherit the GSD framework identity + L0 invariants (cache-stable prefix). Agents do not "read" the design-family sections below; those are passive content the cache covers for free.
25
- 2. **Skills** (`skills/<name>/SKILL.md`) cross-link to specific sections (`./shared-preamble.md#design-quality-pillars`, etc.) instead of restating recurring principle lists inline. This is the D-10 extract-then-link discipline from Phase 28.5: principle text lives in one place; skills point at it.
23
+ 1. **Agents** (`agents/*.md`) import this file via `@reference/shared-preamble.md` to inherit the GDD framework identity + L0 invariants (cache-stable prefix). Agents do not "read" the design-family sections below; those are passive content the cache covers for free.
24
+ 2. **Skills** (`skills/<name>/SKILL.md`) cross-link to specific sections (`./shared-preamble.md#design-quality-pillars`, etc.) instead of restating recurring principle lists inline. This is the extract-then-link discipline: principle text lives in one place; skills point at it.
26
25
 
27
26
  ## Framework Identity
28
27
 
29
- You are a GSD agent operating under the `get-design-done` plugin contract (see `agents/README.md` for the full authoring contract). You are spawned by a pipeline stage (or by another agent) via the Claude Code `Task` tool with a fully self-contained prompt. You have **zero session memory** - everything you need is in the prompt string and the files listed inside its `<required_reading>` block.
28
+ You are a GDD agent operating under the `get-design-done` plugin contract (see `agents/README.md` for the full authoring contract). You are spawned by a pipeline stage (or by another agent) via the Claude Code `Task` tool with a fully self-contained prompt. You have **zero session memory** - everything you need is in the prompt string and the files listed inside its `<required_reading>` block.
30
29
 
31
30
  You are one step in a pipeline. You do not own the pipeline. The orchestrator decides what runs next based on your output.
32
31
 
33
- ## Ordering Convention (D-17)
32
+ ## Ordering Convention
34
33
 
35
34
  Your agent body is structured in this exact order so the cache prefix stays stable:
36
35
 
@@ -42,9 +41,9 @@ Do not reorder. Do not inline this preamble. Do not splice dynamic content ahead
42
41
 
43
42
  ## Pre-Warming
44
43
 
45
- The `/gdd:warm-cache` command (ships in Plan 10.1-02) pre-warms this identical prefix in the Anthropic cache before a design sprint, so the first real agent spawn of the sprint is already a cache hit on the shared-preamble bytes. You do not need to do anything special to participate - just keep the import directive at the top of your body.
44
+ The `/gdd:warm-cache` command pre-warms this identical prefix in the Anthropic cache before a design sprint, so the first real agent spawn of the sprint is already a cache hit on the shared-preamble bytes. You do not need to do anything special to participate - just keep the import directive at the top of your body.
46
45
 
47
- ## Design Philosophy Layer (Phase 19.6)
46
+ ## Design Philosophy Layer
48
47
 
49
48
  The framework is anchored to three design philosophy references that agents may read during brief, audit, and verify stages:
50
49
 
@@ -106,8 +105,8 @@ Probe pattern (used by `skills/darkmode`, `skills/compare`, `skills/figma-write`
106
105
  2. **Live tool call** - invoke a metadata endpoint (e.g., `preview_list`, `get_metadata`). Success → `available`. Error → `unavailable`.
107
106
  3. **Write to STATE.md `<connections>`** - three-value schema (`available | unavailable | not_configured`). Never add new values.
108
107
 
109
- For full per-connection probe scripts (figma, refero, preview, etc.) see the individual `connections/<name>.md` files. For the onboarding wizard flow, see `./connections-onboarding.md` (Phase 28.5 extract).
108
+ For full per-connection probe scripts (figma, refero, preview, etc.) see the individual `connections/<name>.md` files. For the onboarding wizard flow, see `./connections-onboarding.md`.
110
109
 
111
110
  ---
112
111
 
113
- *Imported by: every file under `agents/*.md` (except `agents/README.md`). Cross-linked by: design-family skills under `skills/{audit,style,darkmode,compare,figma-write,connections,benchmark}/SKILL.md`. Maintained as part of Phase 10.1 (OPT-07), Phase 14.5 (L0/L2 split), and Phase 28.5 (Bucket 2 design-family rework - D-10). Edits to this file affect every agent simultaneously - verify across the full agent suite before committing.*
112
+ *Imported by: every file under `agents/*.md` (except `agents/README.md`). Cross-linked by: design-family skills under `skills/{audit,style,darkmode,compare,figma-write,connections,benchmark}/SKILL.md`. Edits to this file affect every agent simultaneously - verify across the full agent suite before committing.*
@@ -9,7 +9,7 @@ is a `composes_with` edge (the source calls the target as sub-orchestration); a
9
9
  a `next_skills` edge (a pipeline hint for what runs next). Stage grouping is best-effort and
10
10
  inferred from the skill name; skills with no stage keyword fall under Utility.
11
11
 
12
- Skills: 94. Composition edges: 0 composes_with, 6 next_skills.
12
+ Skills: 96. Composition edges: 21 composes_with, 6 next_skills.
13
13
 
14
14
  ```mermaid
15
15
  flowchart TD
@@ -91,10 +91,12 @@ flowchart TD
91
91
  n_note["note"]
92
92
  n_openrouter_status["openrouter-status"]
93
93
  n_override["override"]
94
+ n_paper_write["paper-write"]
94
95
  n_pause["pause"]
95
96
  n_peer_cli_add["peer-cli-add"]
96
97
  n_peer_cli_customize["peer-cli-customize"]
97
98
  n_peers["peers"]
99
+ n_pencil_write["pencil-write"]
98
100
  n_pin["pin"]
99
101
  n_plant_seed["plant-seed"]
100
102
  n_pr_branch["pr-branch"]
@@ -122,10 +124,31 @@ flowchart TD
122
124
  n_zoom_out["zoom-out"]
123
125
  end
124
126
 
127
+ n_apply_reflections --> n_audit
128
+ n_brief --> n_explore
125
129
  n_brief -.-> n_explore
130
+ n_compare --> n_verify
131
+ n_complete_cycle --> n_audit
132
+ n_darkmode --> n_audit
133
+ n_design --> n_figma_write
134
+ n_design --> n_paper_write
135
+ n_design --> n_pencil_write
126
136
  n_design -.-> n_verify
137
+ n_discover --> n_explore
138
+ n_discuss --> n_list_assumptions
139
+ n_explore --> n_discuss
140
+ n_explore --> n_list_assumptions
141
+ n_explore --> n_sketch
127
142
  n_explore -.-> n_plan
143
+ n_new_cycle --> n_brief
144
+ n_new_project --> n_brief
128
145
  n_new_project -.-> n_brief
129
146
  n_plan -.-> n_design
147
+ n_scan --> n_explore
148
+ n_ship --> n_pr_branch
149
+ n_sketch --> n_sketch_wrap_up
150
+ n_spike --> n_spike_wrap_up
151
+ n_verify --> n_audit
152
+ n_verify --> n_debug
130
153
  n_verify -.-> n_ship
131
154
  ```
@@ -0,0 +1,373 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Pure-Node port of scripts/bootstrap.sh.
6
+ *
7
+ * Original: scripts/bootstrap.sh — get-design-done SessionStart bootstrap.
8
+ * Auto-provisions companion resources that get-design-done references but
9
+ * which are not Claude Code plugins (so they cannot be listed in `dependencies`).
10
+ *
11
+ * Idempotency: a marker file under ${CLAUDE_PLUGIN_DATA}/bootstrap-manifest.txt
12
+ * is compared byte-for-byte to the bundled scripts/bootstrap-manifest.txt.
13
+ * If they match, the script no-ops — only first install (or a manifest bump)
14
+ * triggers the network/IO work.
15
+ *
16
+ * Behavior preserved from the .sh:
17
+ * - Env-var fallbacks: CLAUDE_PLUGIN_DATA → ~/.claude/plugins/data/get-design-done,
18
+ * CLAUDE_PLUGIN_ROOT → resolved relative to this script (../).
19
+ * - Windows backslash normalization for both env vars (\ → /).
20
+ * - mkdir -p of PLUGIN_DATA, ~/.claude/libs, ~/.claude/skills.
21
+ * - Clone (--depth 1 --quiet) or `git -C <target> pull --quiet --ff-only`
22
+ * for VoltAgent/awesome-design-md. We DO shell out to the `git` CLI
23
+ * (that's a real dep, not bash) via spawnSync — the rule against
24
+ * spawnSync is specifically against spawnSync('bash', …).
25
+ * - Soft-notice (stderr only) if ~/.claude/skills/emil-design-eng is absent.
26
+ * - Ensures cwd/.design/budget.json with the literal default JSON from the .sh
27
+ * (written atomically via .tmp + rename).
28
+ * - Ensures cwd/.design/telemetry/.
29
+ * - Copies manifest → marker on success.
30
+ * - Silent-on-failure: every error path collapses to exit 0. Only logs go to
31
+ * stderr with the `[get-design-done bootstrap]` prefix.
32
+ *
33
+ * Sourcing-guard pattern: helpers are exported on module.exports; main() only
34
+ * runs when this file is the entry point (require.main === module). Tests can
35
+ * require() this module and exercise helpers without triggering the network
36
+ * clone or the cwd/.design/ side effects.
37
+ *
38
+ * Module.exports.run({argv, env, cwd}) accepts optional injection for tests.
39
+ */
40
+
41
+ const fs = require('node:fs');
42
+ const path = require('node:path');
43
+ const os = require('node:os');
44
+ const { spawnSync } = require('node:child_process');
45
+
46
+ const LOG_PREFIX = '[get-design-done bootstrap]';
47
+
48
+ /**
49
+ * Stderr logger matching the .sh `log()` function.
50
+ * @param {string} msg
51
+ */
52
+ function log(msg) {
53
+ // The .sh used: printf '[get-design-done bootstrap] %s\n' "$*" >&2
54
+ process.stderr.write(`${LOG_PREFIX} ${msg}\n`);
55
+ }
56
+
57
+ /**
58
+ * Normalize Windows backslashes to forward slashes, mirroring the bash
59
+ * `${VAR//\\//}` parameter expansion used in the .sh.
60
+ * @param {string|undefined|null} p
61
+ * @returns {string}
62
+ */
63
+ function normalizeSlashes(p) {
64
+ if (p === undefined || p === null) return '';
65
+ return String(p).replace(/\\/g, '/');
66
+ }
67
+
68
+ /**
69
+ * Default for CLAUDE_PLUGIN_DATA — matches the .sh fallback exactly.
70
+ * Returns forward-slash form so the bash-style normalization is a no-op
71
+ * here; the env-var path still gets normalized in resolveContext.
72
+ * @param {string} home
73
+ */
74
+ function defaultPluginData(home) {
75
+ return normalizeSlashes(path.join(home, '.claude', 'plugins', 'data', 'get-design-done'));
76
+ }
77
+
78
+ /**
79
+ * Default for CLAUDE_PLUGIN_ROOT — matches the .sh fallback:
80
+ * `cd "$(dirname "$0")/.." && pwd`
81
+ * In Node terms: the parent of __dirname.
82
+ */
83
+ function defaultPluginRoot() {
84
+ return normalizeSlashes(path.resolve(__dirname, '..'));
85
+ }
86
+
87
+ /**
88
+ * Resolve the full set of paths/flags the script needs.
89
+ * Pure — no IO. Exposed for tests.
90
+ * @param {{env?: NodeJS.ProcessEnv, home?: string}} [opts]
91
+ */
92
+ function resolveContext(opts = {}) {
93
+ const env = opts.env || process.env;
94
+ const home = opts.home || os.homedir();
95
+
96
+ const pluginDataRaw = env.CLAUDE_PLUGIN_DATA || defaultPluginData(home);
97
+ const pluginData = normalizeSlashes(pluginDataRaw);
98
+
99
+ const pluginRootRaw = env.CLAUDE_PLUGIN_ROOT || defaultPluginRoot();
100
+ const pluginRoot = normalizeSlashes(pluginRootRaw);
101
+
102
+ const manifest = `${pluginRoot}/scripts/bootstrap-manifest.txt`;
103
+ const marker = `${pluginData}/bootstrap-manifest.txt`;
104
+
105
+ return {
106
+ home,
107
+ pluginData,
108
+ pluginRoot,
109
+ manifest,
110
+ marker,
111
+ libsDir: path.join(home, '.claude', 'libs'),
112
+ skillsDir: path.join(home, '.claude', 'skills'),
113
+ awesomeRepoTarget: path.join(home, '.claude', 'libs', 'awesome-design-md'),
114
+ emilSkillTarget: path.join(home, '.claude', 'skills', 'emil-design-eng'),
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Best-effort `mkdir -p`. Swallows EEXIST; logs and swallows everything else.
120
+ * @param {string} dir
121
+ */
122
+ function ensureDir(dir) {
123
+ try {
124
+ fs.mkdirSync(dir, { recursive: true });
125
+ } catch (err) {
126
+ if (err && err.code !== 'EEXIST') {
127
+ log(`mkdir failed for ${dir} (${err.code || err.message}) — continuing`);
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Byte-for-byte comparison of two files. Returns true only if both exist and
134
+ * have identical contents. Any error path → false (we'd rather re-run than
135
+ * skip).
136
+ * @param {string} a
137
+ * @param {string} b
138
+ */
139
+ function filesEqual(a, b) {
140
+ try {
141
+ if (!fs.existsSync(a) || !fs.existsSync(b)) return false;
142
+ const bufA = fs.readFileSync(a);
143
+ const bufB = fs.readFileSync(b);
144
+ if (bufA.length !== bufB.length) return false;
145
+ return bufA.equals(bufB);
146
+ } catch {
147
+ return false;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Match the .sh `clone_or_update`:
153
+ * - target/.git exists → `git -C target pull --quiet --ff-only`, log on fail
154
+ * - target exists, no .git → log+skip
155
+ * - target absent → `git clone --quiet --depth 1 <url> <target>`, log on fail
156
+ *
157
+ * We invoke the `git` CLI directly via spawnSync. spawnSync('git', …) is fine —
158
+ * the prohibition is on spawnSync('bash', …).
159
+ *
160
+ * @param {string} repoUrl
161
+ * @param {string} target
162
+ */
163
+ function cloneOrUpdate(repoUrl, target) {
164
+ let isGitCheckout = false;
165
+ let targetExists = false;
166
+ try {
167
+ targetExists = fs.existsSync(target);
168
+ if (targetExists) {
169
+ isGitCheckout = fs.existsSync(path.join(target, '.git'));
170
+ }
171
+ } catch {
172
+ // fall through — treat as absent
173
+ }
174
+
175
+ if (isGitCheckout) {
176
+ log(`updating ${target}`);
177
+ const r = spawnSync('git', ['-C', target, 'pull', '--quiet', '--ff-only'], {
178
+ stdio: ['ignore', 'ignore', 'ignore'],
179
+ windowsHide: true,
180
+ });
181
+ if (r.error || r.status !== 0) {
182
+ log(`pull failed for ${target} (continuing)`);
183
+ }
184
+ return;
185
+ }
186
+
187
+ if (targetExists) {
188
+ log(`${target} exists and is not a git checkout — skipping`);
189
+ return;
190
+ }
191
+
192
+ // Defense in depth: refuse repoUrl / target arguments that look like git
193
+ // CLI flags (e.g. --upload-pack=evil). Even though both args originate
194
+ // from compile-time constants in resolveContext(), a future refactor
195
+ // could let env-derived values reach this point — fail closed.
196
+ if (typeof repoUrl !== 'string' || repoUrl.startsWith('-') ||
197
+ typeof target !== 'string' || target.startsWith('-')) {
198
+ log(`refusing suspicious clone args for ${repoUrl} -> ${target}`);
199
+ return;
200
+ }
201
+
202
+ log(`cloning ${repoUrl} -> ${target}`);
203
+ // Use `--` to terminate option parsing so a malicious URL that looks
204
+ // like a flag is treated as a positional arg by git.
205
+ const r = spawnSync('git', ['clone', '--quiet', '--depth', '1', '--', repoUrl, target], {
206
+ stdio: ['ignore', 'ignore', 'ignore'],
207
+ windowsHide: true,
208
+ });
209
+ if (r.error || r.status !== 0) {
210
+ log(`clone failed for ${repoUrl}`);
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Default budget.json content — copied verbatim from the heredoc in the .sh
216
+ * (BUDGET_EOF block, lines 60–69). Trailing newline preserved to match
217
+ * `cat > file <<'EOF'` output.
218
+ */
219
+ const DEFAULT_BUDGET_JSON = `{
220
+ "per_task_cap_usd": 2.00,
221
+ "per_phase_cap_usd": 20.00,
222
+ "tier_overrides": {},
223
+ "auto_downgrade_on_cap": true,
224
+ "cache_ttl_seconds": 3600,
225
+ "enforcement_mode": "enforce"
226
+ }
227
+ `;
228
+
229
+ /**
230
+ * Atomic write: write to <dest>.tmp then rename. Silent on failure.
231
+ * @param {string} dest
232
+ * @param {string} content
233
+ */
234
+ function atomicWrite(dest, content) {
235
+ const tmp = `${dest}.tmp`;
236
+ try {
237
+ fs.writeFileSync(tmp, content);
238
+ fs.renameSync(tmp, dest);
239
+ return true;
240
+ } catch (err) {
241
+ log(`write failed for ${dest} (${err && (err.code || err.message)}) — continuing`);
242
+ // Best-effort cleanup of the .tmp; ignore any error.
243
+ try { fs.unlinkSync(tmp); } catch {}
244
+ return false;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Ensure cwd/.design/budget.json (with defaults) and cwd/.design/telemetry/.
250
+ * Mirrors lines 56–73 of the .sh.
251
+ * @param {string} cwd
252
+ */
253
+ function ensureDesignDir(cwd) {
254
+ const designDir = path.join(cwd, '.design');
255
+ ensureDir(designDir);
256
+
257
+ const budgetPath = path.join(designDir, 'budget.json');
258
+ let budgetExists = false;
259
+ try {
260
+ budgetExists = fs.existsSync(budgetPath);
261
+ } catch {
262
+ budgetExists = false;
263
+ }
264
+ if (!budgetExists) {
265
+ atomicWrite(budgetPath, DEFAULT_BUDGET_JSON);
266
+ }
267
+
268
+ ensureDir(path.join(designDir, 'telemetry'));
269
+ }
270
+
271
+ /**
272
+ * Best-effort `cp manifest marker`. The .sh wraps this in `if [[ -f MANIFEST ]]`.
273
+ * @param {string} manifest
274
+ * @param {string} marker
275
+ */
276
+ function copyManifestToMarker(manifest, marker) {
277
+ try {
278
+ if (!fs.existsSync(manifest)) return;
279
+ } catch {
280
+ return;
281
+ }
282
+ try {
283
+ const data = fs.readFileSync(manifest);
284
+ // Write atomically so a partial copy doesn't leave a half-written marker
285
+ // that would later equal-compare false but trigger weird states.
286
+ const tmp = `${marker}.tmp`;
287
+ fs.writeFileSync(tmp, data);
288
+ fs.renameSync(tmp, marker);
289
+ } catch (err) {
290
+ log(`copy manifest→marker failed (${err && (err.code || err.message)}) — continuing`);
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Main entry — equivalent to executing bootstrap.sh top-to-bottom.
296
+ * Always returns 0 (silent-on-failure policy from the .sh: `set -u` + final
297
+ * `exit 0`; no `set -e`, every IO action is guarded). Optional opts allow
298
+ * tests to inject env/cwd/home without mutating process state.
299
+ *
300
+ * @param {{env?: NodeJS.ProcessEnv, cwd?: string, home?: string}} [opts]
301
+ * @returns {number} exit code (always 0)
302
+ */
303
+ function run(opts = {}) {
304
+ const ctx = resolveContext({ env: opts.env, home: opts.home });
305
+ const cwd = opts.cwd || process.cwd();
306
+
307
+ // mkdir -p "${PLUGIN_DATA}" "${HOME}/.claude/libs" "${HOME}/.claude/skills"
308
+ ensureDir(ctx.pluginData);
309
+ ensureDir(ctx.libsDir);
310
+ ensureDir(ctx.skillsDir);
311
+
312
+ // Early-exit: bundled manifest matches last-run marker.
313
+ if (filesEqual(ctx.manifest, ctx.marker)) {
314
+ return 0;
315
+ }
316
+
317
+ // Required library: VoltAgent/awesome-design-md.
318
+ cloneOrUpdate(
319
+ 'https://github.com/VoltAgent/awesome-design-md.git',
320
+ ctx.awesomeRepoTarget
321
+ );
322
+
323
+ // Soft notice for companion skills we cannot auto-install.
324
+ try {
325
+ if (!fs.existsSync(ctx.emilSkillTarget)) {
326
+ log('optional: emil-design-eng skill not found in ~/.claude/skills. See get-design-done README for install options.');
327
+ }
328
+ } catch {
329
+ // ignore — emil notice is purely advisory
330
+ }
331
+
332
+ // Phase 10.1: .design/budget.json + .design/telemetry/ (D-12).
333
+ ensureDesignDir(cwd);
334
+
335
+ // Record success so we don't re-run until the bundled manifest changes.
336
+ copyManifestToMarker(ctx.manifest, ctx.marker);
337
+
338
+ return 0;
339
+ }
340
+
341
+ module.exports = {
342
+ run,
343
+ // Helpers exported so test/suite/* can exercise them in isolation
344
+ // (sourcing-guard equivalent).
345
+ log,
346
+ normalizeSlashes,
347
+ defaultPluginData,
348
+ defaultPluginRoot,
349
+ resolveContext,
350
+ ensureDir,
351
+ filesEqual,
352
+ cloneOrUpdate,
353
+ atomicWrite,
354
+ ensureDesignDir,
355
+ copyManifestToMarker,
356
+ DEFAULT_BUDGET_JSON,
357
+ LOG_PREFIX,
358
+ };
359
+
360
+ // Run main() only when invoked as the entry point.
361
+ if (require.main === module) {
362
+ // Match the .sh: always exit 0 unless a programmer error blows up.
363
+ // run() never throws; if it ever did, we'd still rather no-op silently
364
+ // than crash a SessionStart hook.
365
+ try {
366
+ process.exit(run());
367
+ } catch (err) {
368
+ // Last-resort guard. Surface to stderr (the hook is fire-and-forget)
369
+ // then exit 0 to match silent-on-failure.
370
+ try { log(`unhandled error: ${err && (err.stack || err.message || String(err))} — exiting 0`); } catch {}
371
+ process.exit(0);
372
+ }
373
+ }
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+ // Shared prompt-injection patterns — single source of truth for both
3
+ // hooks/gdd-read-injection-scanner.js (runtime hook) and
4
+ // scripts/run-injection-scanner-ci.cjs (CI scanner).
5
+ // Add new patterns here; both consumers pick them up automatically.
6
+ //
7
+ // Phase 14.5 adds three new families: invisible-Unicode obfuscation,
8
+ // HTML-comment instruction hijacks, and secret-exfil trigger patterns.
9
+
10
+ // Zero-width + word-joiner + BOM + bidi overrides. Used for detection
11
+ // AND as a normalization stripper for hooks that run scan after NFKC.
12
+ const _CONTEXT_INVISIBLE_CHARS = /[\u200B-\u200D\u2060\uFEFF\u202A-\u202E]/;
13
+
14
+ const INJECTION_PATTERNS = [
15
+ // ── classic prompt-injection verbs ──────────────────────────────────
16
+ { name: 'ignore previous', re: /ignore\s+(all\s+)?(previous|prior|above)\s+instructions?/i },
17
+ { name: 'disregard previous', re: /disregard\s+(all\s+)?(previous|prior|above)\s+instructions?/i },
18
+ { name: 'forget previous', re: /forget\s+(the\s+|all\s+)?(previous|prior|above)/i },
19
+ { name: 'you are now a different', re: /you\s+are\s+now\s+a\s+different/i },
20
+ { name: 'system: you are', re: /system\s*:\s*you\s+are/i },
21
+ { name: 'role tag injection', re: /<\s*\/?\s*(system|assistant|human)\s*>/i },
22
+ { name: '[INST] fragment', re: /\[INST\]/i },
23
+ { name: '### instruction fragment',re: /###\s*instruction/i },
24
+
25
+ // ── invisible-Unicode obfuscation (14.5 new family) ─────────────────
26
+ { name: 'invisible-unicode chars', re: _CONTEXT_INVISIBLE_CHARS },
27
+ { name: 'bidi-override instruction', re: /[\u202A-\u202E][^\n]*(ignore|disregard|forget|system\s*:)/i },
28
+
29
+ // ── HTML-comment / hidden-element instruction hijack (14.5 new) ─────
30
+ { name: 'html-comment system', re: /<!--\s*system\s*:/i },
31
+ { name: 'html-comment assistant', re: /<!--\s*assistant\s*:/i },
32
+ { name: 'html-comment ignore', re: /<!--\s*(ignore|disregard|forget)\b/i },
33
+ { name: 'hidden div system', re: /<div\s+[^>]*style\s*=\s*["'][^"']*display\s*:\s*none[^"']*["'][^>]*>\s*(system|ignore|disregard)/i },
34
+ { name: 'hidden span system', re: /<span\s+[^>]*style\s*=\s*["'][^"']*visibility\s*:\s*hidden[^"']*["'][^>]*>\s*(system|ignore|disregard)/i },
35
+ { name: 'zero-font-size trick', re: /style\s*=\s*["'][^"']*font-size\s*:\s*0[^"']*["'][^>]*>\s*(ignore|system|disregard)/i },
36
+
37
+ // ── secret-exfil trigger patterns (14.5 new) ─────────────────────────
38
+ { name: 'curl-with-api-key-env', re: /curl\s+[^|\n]*\$\{?[A-Z][A-Z0-9_]*_(KEY|TOKEN|SECRET|PASSWORD|AUTH)\}?/ },
39
+ { name: 'cat-dotenv', re: /\bcat\s+\.env(\.[a-z]+)?\b/ },
40
+ { name: 'printenv-leak', re: /\bprintenv\b[^\n]{0,80}\|\s*(curl|wget|nc|ssh)/ },
41
+ { name: 'tar-home-netcat', re: /\btar\s+c[fzvj]+\s+-\s+~[^\n]*\|\s*(nc|ssh|curl)/ },
42
+ { name: 'env-dot-leak', re: /process\.env\.[A-Z][A-Z0-9_]*_(KEY|TOKEN|SECRET)\s*[^;,\n]*(fetch|axios|XMLHttpRequest|http\.request)/ },
43
+ { name: 'ssh-key-cat', re: /\bcat\s+~?\/?\.ssh\/id_(rsa|ed25519|ecdsa|dsa)\b/ },
44
+ ];
45
+
46
+ /**
47
+ * Apply patterns to content and return matched pattern names (deduped).
48
+ */
49
+ function scan(content) {
50
+ if (typeof content !== 'string' || !content) return [];
51
+ const hits = [];
52
+ for (const { name, re } of INJECTION_PATTERNS) {
53
+ if (re.test(content)) hits.push(name);
54
+ }
55
+ return hits;
56
+ }
57
+
58
+ module.exports = { INJECTION_PATTERNS, _CONTEXT_INVISIBLE_CHARS, scan };