symphony-forge 0.1.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.
Files changed (4) hide show
  1. package/LICENSE +173 -0
  2. package/README.md +138 -0
  3. package/dist/index.js +1459 -0
  4. package/package.json +84 -0
package/dist/index.js ADDED
@@ -0,0 +1,1459 @@
1
+ #!/usr/bin/env node
2
+ import{program as S}from"commander";import{existsSync as C,readdirSync as D,readFileSync as N,statSync as ye}from"fs";import{join as k}from"path";import{log as b}from"@clack/prompts";var be=/\.md$/,B=(e,t,a,s)=>{if(C(e))for(let o of D(e,{withFileTypes:!0})){if(!o.isDirectory())continue;new RegExp(`^ ${o.name}:`,"m").test(a)||s.push(`${t}/${o.name} not listed in topology.yaml`)}},ve=e=>{let t=k(e,".control","topology.yaml"),a=[];if(!C(t))return{category:"Topology Coverage",issues:[".control/topology.yaml not found (install control layer)"],passed:!1};let s=N(t,"utf-8");return B(k(e,"apps"),"apps",s,a),B(k(e,"packages"),"packages",s,a),{category:"Topology Coverage",issues:a,passed:a.length===0}},we=e=>{let t=k(e,"docs"),a=[];if(!C(t))return{category:"Stale Docs",issues:["docs/ directory not found (install knowledge layer)"],passed:!1};let s=2160*60*60*1e3,o=Date.now(),n=r=>{for(let c of D(r,{withFileTypes:!0})){let m=k(r,c.name);if(c.isDirectory())c.name!=="_templates"&&n(m);else if(c.name.endsWith(".md")){let p=ye(m),d=o-p.mtimeMs;if(d>s){let R=Math.floor(d/864e5),O=m.replace(`${e}/`,"");a.push(`${O} (${R} days old)`)}}}};return n(t),{category:"Stale Docs",issues:a,passed:a.length===0}},$e=e=>{let t=k(e,"docs"),a=k(t,"_index.md"),s=[];if(!C(a))return{category:"Index Coverage",issues:["docs/_index.md not found"],passed:!1};let o=N(a,"utf-8"),n=r=>{for(let c of D(r,{withFileTypes:!0})){let m=k(r,c.name);if(c.isDirectory())c.name!=="_templates"&&n(m);else if(c.name.endsWith(".md")&&c.name!=="_index.md"){let p=m.replace(`${t}/`,""),d=p.replace(be,"");o.includes(d)||o.includes(p)||s.push(`${p} not referenced in _index.md`)}}};return n(t),{category:"Index Coverage",issues:s,passed:s.length===0}},Se=e=>{let t=k(e,".symphony-forge.json"),a=[];if(C(t))try{let s=N(t,"utf-8");JSON.parse(s)}catch{a.push(".symphony-forge.json is not valid JSON")}else a.push(".symphony-forge.json not found (run symphony-forge layer to create)");return{category:"Manifest",issues:a,passed:a.length===0}},H=()=>{b.info(`=== Symphony Forge \u2014 Entropy Audit ===
3
+ `);let e=process.cwd(),t=[Se(e),ve(e),we(e),$e(e)],a=0;for(let s of t){let o=s.passed?"\u2713":"\u2717";if(b.info(`[${o}] ${s.category}`),!s.passed){for(let n of s.issues)b.warn(` ${n}`);a+=s.issues.length}}b.info(""),a>0?(b.error(`Found ${a} issue(s). Review and address above warnings.`),process.exit(1)):b.info("No issues found. Repository is in good shape.")};import{copyFile as Ae,readdir as xe,readFile as L,rm as j,writeFile as M}from"fs/promises";import{join as h}from"path";import{cancel as Y,intro as Ee,isCancel as z,log as Me,outro as Ie,select as Oe,spinner as De,text as Ne}from"@clack/prompts";import{detectPackageManager as Te,installDependencies as _e}from"nypm";import{spawnSync as Re}from"child_process";import{readFile as Ce}from"fs/promises";import{join as E}from"path";var P="https://github.com/vercel/next-forge",q=e=>e.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\\/g,"/"),Pe=process.platform==="win32",g=(e,t,a)=>{let s=Re(e,t,{stdio:"inherit",shell:Pe,...a});if(s.error)throw s.error;if(s.status!==0){let o=s.stderr?.toString().trim(),n=o?`Command failed: ${e} ${t.join(" ")}
4
+ ${o}`:`Command failed with exit code ${s.status}: ${e} ${t.join(" ")}`;throw new Error(n)}return s},T=[E(".github","workflows"),"docs"],_=[E(".github","CONTRIBUTING.md"),E(".github","FUNDING.yml"),E(".github","SECURITY.md"),".changeset","CHANGELOG.md","license.md"],K=[...T,..._];var v="next-forge-update",V=async()=>{let e=await Ce("CHANGELOG.md","utf-8"),t=/# v(\d+\.\d+\.\d+)/g;return[...e.matchAll(t)].map(s=>s[1]).sort((s,o)=>{let[n,r,c]=s.split(".").map(Number),[m,p,d]=o.split(".").map(Number);return n!==m?m-n:r!==p?p-r:d-c})};var X=["bun","npm","yarn","pnpm"],je=(e,t,a)=>{let s=a?`${P}/tree/${a}`:P;g("npx",["create-next-app@latest",e,"--example",s,"--disable-git","--skip-install",`--use-${t}`])},Le=async()=>{for(let e of T)await j(e,{recursive:!0,force:!0});for(let e of _)await j(e,{force:!0})},Fe=async e=>{await _e({packageManager:{name:e,command:e},silent:!0})},Ge=()=>{g("git",["init"]),g("git",["add","."]),g("git",["commit","-m","\u2728 Initial commit"])},Ue=async()=>{let e=[{source:h("apps","api"),target:".env.local"},{source:h("apps","app"),target:".env.local"},{source:h("apps","web"),target:".env.local"},{source:h("packages","cms"),target:".env.local"},{source:h("packages","database"),target:".env"},{source:h("packages","internationalization"),target:".env.local"}];for(let{source:t,target:a}of e)await Ae(h(t,".env.example"),h(t,a))},We=e=>{g(e,["run","build",e==="npm"?"--workspace":"--filter","@repo/database"])},Be=async(e,t)=>{let a=h(e,"package.json"),s=await L(a,"utf8"),o=JSON.parse(s);t==="pnpm"?o.packageManager="pnpm@10.31.0":t==="npm"?o.packageManager="npm@10.8.1":t==="yarn"&&(o.packageManager="yarn@1.22.22");let n=JSON.stringify(o,null,2);await M(a,`${n}
5
+ `)},He=async(e,t)=>{let a=h(e,"package.json"),s=await L(a,"utf8"),o=JSON.parse(s);t==="pnpm"&&(o.workspaces=void 0,await M(h(e,"pnpm-workspace.yaml"),`packages:
6
+ - 'apps/*'
7
+ - 'packages/*'
8
+ `));let n=JSON.stringify(o,null,2);await M(a,`${n}
9
+ `),await j("bun.lock",{force:!0})},J=async e=>{let t=await L(e,"utf8"),a=JSON.parse(t);if(a.dependencies){let o=Object.entries(a.dependencies);for(let[n,r]of o)r==="workspace:*"&&(a.dependencies[n]="*")}if(a.devDependencies){let o=Object.entries(a.devDependencies);for(let[n,r]of o)r==="workspace:*"&&(a.devDependencies[n]="*")}let s=JSON.stringify(a,null,2);await M(e,`${s}
10
+ `)},qe=async e=>{let t=h(e,"package.json");await J(t);let a=["apps","packages"];for(let s of a){let o=h(e,s),n=await xe(o);for(let r of n){let c=h(o,r,"package.json");await J(c)}}},Ke=async()=>{let e=await Ne({message:"What is your project named?",placeholder:"my-app",validate(t){if(t.length===0)return"Please enter a project name."}});return z(e)&&(Y("Operation cancelled."),process.exit(0)),e.toString()},Ve=async()=>{let e=await Te(process.cwd());if(e)return e.name;let t=await Oe({message:"Which package manager would you like to use?",options:X.map(a=>({value:a,label:a})),initialValue:"bun"});return z(t)&&(Y("Operation cancelled."),process.exit(0)),t},Q=async e=>{try{Ee("Let's start a next-forge project!");let t=process.cwd(),a=e.name||await Ke(),s=e.packageManager||await Ve();if(!X.includes(s))throw new Error("Invalid package manager");let o=De(),n=h(t,a);o.start("Cloning next-forge..."),je(a,s,e.branch),o.message("Moving into repository..."),process.chdir(n),s!=="bun"&&(o.message("Updating package manager configuration..."),await Be(n,s),o.message("Updating workspace config..."),await He(n,s),s!=="pnpm"&&(o.message("Updating workspace dependencies..."),await qe(n))),o.message("Setting up environment variable files..."),await Ue(),o.message("Deleting internal content..."),await Le(),o.message("Installing dependencies..."),await Fe(s),o.message("Setting up ORM..."),We(s),e.disableGit||(o.message("Initializing Git repository..."),Ge()),o.stop("Project initialized successfully!"),Ie("Please make sure you install the Mintlify CLI and Stripe CLI before starting the project.")}catch(t){let a=t instanceof Error?t.message:`Failed to initialize project: ${t}`;Me.error(a),process.exit(1)}};import{existsSync as Mt}from"fs";import{readFile as ie}from"fs/promises";import{join as U}from"path";import{cancel as ce,confirm as It,intro as Ot,isCancel as le,log as $,outro as Dt,select as Nt,spinner as Tt}from"@clack/prompts";import{detectPackageManager as _t}from"nypm";var u=(e,t,a)=>{let s=["#!/usr/bin/env bash","set -euo pipefail","",`# ${e}`,`# Usage: ${t}`];return a&&s.push(`# Timeout: ${a}`),s.push("",'REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"','cd "$REPO_ROOT"',""),s.join(`
11
+ `)},l=(e,t)=>{switch(e.packageManager){case"bun":return`bun run ${t}`;case"yarn":return`yarn ${t}`;case"pnpm":return`pnpm run ${t}`;default:return`npm run ${t}`}},F=(e,t)=>{switch(e.packageManager){case"bun":return`bunx ${t}`;case"yarn":return`yarn dlx ${t}`;case"pnpm":return`pnpm dlx ${t}`;default:return`npx ${t}`}},y=(e,t=!1)=>{switch(e.packageManager){case"bun":return t?"bun install --frozen-lockfile":"bun install";case"yarn":return t?"yarn install --frozen-lockfile":"yarn install";case"pnpm":return t?"pnpm install --frozen-lockfile":"pnpm install";default:return t?"npm ci":"npm install"}},w=e=>{switch(e.packageManager){case"bun":return"bun.lock";case"yarn":return"yarn.lock";case"pnpm":return"pnpm-lock.yaml";default:return"package-lock.json"}};var i=(e,t)=>e.layers.includes(t);var Je=e=>{let t=i(e,"harness")?"make -f Makefile.control audit":"echo 'Install harness layer for audit'",a=i(e,"harness")?"make -f Makefile.control check":`${e.packageManager} run check`,s=i(e,"harness")?"make -f Makefile.control test":`${e.packageManager} run test`;return`# .control/egri.yaml \u2014 EGRI Self-Improvement Loop for ${e.name}
12
+ # EGRI: Evaluate \u2192 Generate \u2192 Rank \u2192 Integrate
13
+ #
14
+ # This config defines the surfaces the system can mutate, the evaluators
15
+ # that judge mutations, and the criteria for promoting changes.
16
+
17
+ loop:
18
+ name: "egri"
19
+ description: "Continuous improvement loop for ${e.name}"
20
+
21
+ # --- Evaluate ---
22
+ # Metrics and checks that measure current system state
23
+ evaluators:
24
+ build:
25
+ command: "${e.packageManager} run build"
26
+ description: "Full build must succeed"
27
+ blocking: true
28
+
29
+ typecheck:
30
+ command: "${a}"
31
+ description: "Lint + typecheck must pass"
32
+ blocking: true
33
+
34
+ tests:
35
+ command: "${s}"
36
+ description: "All tests must pass"
37
+ blocking: true
38
+
39
+ audit:
40
+ command: "${t}"
41
+ description: "Entropy audit must pass (topology, stale docs, wikilinks)"
42
+ blocking: false
43
+
44
+ # --- Generate ---
45
+ # Surfaces the system is allowed to mutate
46
+ mutable_surfaces:
47
+ scripts:
48
+ paths:
49
+ - "scripts/harness/*.sh"
50
+ description: "Build automation scripts"
51
+ constraints:
52
+ - "Must maintain bash strict mode (set -euo pipefail)"
53
+ - "Must use REPO_ROOT pattern for path resolution"
54
+
55
+ docs:
56
+ paths:
57
+ - "docs/**/*.md"
58
+ description: "Knowledge graph documents"
59
+ constraints:
60
+ - "Must have valid frontmatter with tags"
61
+ - "Must be referenced in docs/_index.md"
62
+ - "Wikilinks must resolve to existing files"
63
+
64
+ control:
65
+ paths:
66
+ - ".control/*.yaml"
67
+ description: "Control metalayer configuration"
68
+ constraints:
69
+ - "Must be valid YAML"
70
+ - "topology.yaml must cover all apps/ and packages/"
71
+
72
+ agent_instructions:
73
+ paths:
74
+ - "CLAUDE.md"
75
+ - "AGENTS.md"
76
+ description: "Agent governance instructions"
77
+ constraints:
78
+ - "Must reference installed layers"
79
+ - "Must include working protocol"
80
+
81
+ # Surfaces that must NOT be mutated by the loop
82
+ immutable_surfaces:
83
+ - "packages/database/prisma/schema.prisma"
84
+ - ".env*"
85
+ - "package.json"
86
+ - "${w(e)}"
87
+
88
+ # --- Rank ---
89
+ # How to compare before/after states
90
+ ranking:
91
+ strategy: "evaluator-pass-count"
92
+ description: "A mutation is better if it passes more evaluators without breaking any blocking ones"
93
+ tiebreaker: "fewer-files-changed"
94
+
95
+ # --- Integrate ---
96
+ # When and how to promote successful mutations
97
+ promotion:
98
+ criteria:
99
+ - "All blocking evaluators pass"
100
+ - "No new lint warnings introduced"
101
+ - "Audit score equal or better than before"
102
+ method: "commit-to-branch"
103
+ branch_prefix: "egri/"
104
+ requires_review: true
105
+ `},Z={name:"autoany",description:"EGRI self-improvement loop configuration (.control/egri.yaml)",dependsOn:["control"],generate:e=>[{path:".control/egri.yaml",content:Je(e)}]};var Ye=e=>{let t=e.packageManager,a=y(e),s=i(e,"control")?`## Control Harness
106
+
107
+ Policy gates and automation scripts live alongside the code:
108
+
109
+ - **\`.control/policy.yaml\`** \u2014 Risk gates for database migrations, dependency updates, env changes, new packages, deploys
110
+ - **\`.control/commands.yaml\`** \u2014 Canonical command definitions (smoke, check, test, build, ci, docs-check, deploy)
111
+ - **\`.control/topology.yaml\`** \u2014 Full repo map: all apps, packages, knowledge entry points
112
+ `:"## Control Harness\n\n> [!warning]\n> The `control` layer is not installed. Run `npx symphony-forge layer control` to add governance gates.\n",o=i(e,"harness")?"- **`scripts/harness/`** \u2014 Executable bash scripts for each command\n- **`Makefile.control`** \u2014 `make -f Makefile.control <target>` to run any harness command\n\nKey commands:\n- `make -f Makefile.control smoke` \u2014 quick validation (~120s)\n- `make -f Makefile.control check` \u2014 lint + typecheck (~60s)\n- `make -f Makefile.control ci` \u2014 full pipeline: check + test + build (~600s)\n- `make -f Makefile.control audit` \u2014 entropy audit: topology coverage, stale docs, broken links\n":"> [!warning]\n> The `harness` layer is not installed. Run `npx symphony-forge layer harness` to add build automation.\n",n=i(e,"knowledge")?"## Knowledge Graph\n\nThe knowledge graph lives in `docs/` using Obsidian-flavored Markdown with frontmatter tags.\n\n- **Entry point**: `docs/_index.md` \u2014 full graph map, tag taxonomy, traversal instructions\n- **Architecture**: `docs/architecture/` \u2014 system diagrams, app descriptions\n- **Decisions**: `docs/decisions/` \u2014 Architecture Decision Records (ADRs)\n- **Runbooks**: `docs/runbooks/` \u2014 step-by-step operational procedures\n- **Templates**: `docs/_templates/` \u2014 templates for new docs\n\nLinking conventions: `[[architecture/overview]]` for wikilinks, Mermaid for diagrams, `> [!decision]` / `> [!warning]` for callouts.\n":"## Knowledge Graph\n\n> [!warning]\n> The `knowledge` layer is not installed. Run `npx symphony-forge layer knowledge` to add the documentation skeleton.\n",r=`## Dependency Management
113
+
114
+ Library updates and dependency hygiene are a first-class concern:
115
+
116
+ - **Batch updates by type** \u2014 prefer updating at the workspace root rather than individual packages.
117
+ - **Risk tiers**:
118
+ - **Patch versions**: Low risk \u2014 batch and merge freely after build passes.
119
+ - **Minor versions**: Medium risk \u2014 review changelogs, run full test suite.
120
+ - **Major versions**: High risk \u2014 review migration guides, test thoroughly.
121
+ - **Type-only updates** (\`@types/*\`): Low risk \u2014 update freely, just ensure typecheck passes.
122
+ - **After updating**: Always run \`${a}\`, then \`make -f Makefile.control check\` and \`make -f Makefile.control test\` at minimum.
123
+ `,c=i(e,"autoany")?`## Self-Improvement (EGRI)
124
+
125
+ The EGRI (Evaluate \u2192 Generate \u2192 Rank \u2192 Integrate) loop is configured in \`.control/egri.yaml\`.
126
+
127
+ - **Mutable surfaces**: Things the system can change (scripts, docs, config)
128
+ - **Immutable evaluators**: Metrics that judge changes (build pass, test pass, lint clean)
129
+ - **Promotion criteria**: When to accept a change into the main branch
130
+ `:"",m=`## Working Protocol
131
+
132
+ Follow this protocol for every task:
133
+
134
+ 1. **Read \`AGENTS.md\`** \u2014 understand commands, constraints, and knowledge graph structure
135
+ ${i(e,"knowledge")?"2. **Traverse `docs/`** \u2014 start at `docs/_index.md`, navigate to domain-specific docs\n":""}${i(e,"control")?"3. **Check policy** \u2014 run `make -f Makefile.control policy-check` to see if changes trigger risk gates\n":""}4. **Implement** \u2014 follow the constraints in AGENTS.md
136
+ ${i(e,"knowledge")?"5. **Update docs** \u2014 any schema, API, or env var change must have a corresponding doc update in `docs/`\n":""}${i(e,"harness")?"6. **Run checks** \u2014 `make -f Makefile.control check` before committing; `make -f Makefile.control ci` before pushing\n":""}`;return`# CLAUDE.md \u2014 ${e.name}
137
+
138
+ ## Project
139
+ ${e.name}${e.description?` \u2014 ${e.description}`:""}
140
+
141
+ ## Stack
142
+ - next-forge (Turborepo + Next.js 15)
143
+ - ${t==="bun"?"Bun":t} as package manager
144
+
145
+ ## Commands
146
+ - \`${a}\` \u2014 install dependencies
147
+ - \`${l(e,"dev")}\` \u2014 start all apps in dev mode
148
+ - \`${l(e,"build")}\` \u2014 build all apps
149
+ - \`${l(e,"check")}\` \u2014 lint all packages
150
+
151
+ ## Conventions
152
+ - App Router (Next.js) \u2014 no pages/ directory
153
+ - Server Components by default, 'use client' only when needed
154
+ - Shared UI components in packages/design-system
155
+ - Database schema in packages/database (Prisma)
156
+
157
+ ${n}
158
+ ${s}
159
+ ${o}
160
+ ${r}
161
+ ${c}
162
+ ${m}
163
+ `},ze=e=>{let t=e.packageManager,a=i(e,"harness")?`## Commands
164
+
165
+ All commands are defined in \`.control/commands.yaml\` and accessible via \`Makefile.control\`:
166
+
167
+ | Command | What it does | When to use |
168
+ |---------|-------------|-------------|
169
+ | \`make -f Makefile.control smoke\` | Install + check + build app | After pulling changes |
170
+ | \`make -f Makefile.control check\` | Lint + typecheck | Before every commit |
171
+ | \`make -f Makefile.control test\` | Run test suite | Before pushing |
172
+ | \`make -f Makefile.control build\` | Full build | Before deploy |
173
+ | \`make -f Makefile.control ci\` | check + test + build | Full local CI simulation |
174
+ | \`make -f Makefile.control docs-check\` | Verify docs freshness | After docs/ changes |
175
+ | \`make -f Makefile.control policy-check\` | Policy gate warnings | Before committing |
176
+ | \`make -f Makefile.control audit\` | Full entropy audit | Periodic health check |
177
+
178
+ Direct ${t} commands also work:
179
+ - \`${y(e)}\` \u2014 install dependencies
180
+ - \`${l(e,"dev")}\` \u2014 start all apps in dev mode
181
+ - \`${l(e,"build")}\` \u2014 build all apps
182
+ - \`${l(e,"test")}\` \u2014 run tests
183
+ - \`${l(e,"check")}\` \u2014 lint
184
+ `:`## Commands
185
+
186
+ - \`${y(e)}\` \u2014 install dependencies
187
+ - \`${l(e,"dev")}\` \u2014 start all apps in dev mode
188
+ - \`${l(e,"build")}\` \u2014 build all apps
189
+ - \`${l(e,"test")}\` \u2014 run tests
190
+
191
+ > [!warning]
192
+ > The \`harness\` layer is not installed. Run \`npx symphony-forge layer harness\` for build automation via \`Makefile.control\`.
193
+ `,s=i(e,"knowledge")?`## Knowledge Graph Traversal
194
+
195
+ The knowledge graph lives in \`docs/\` using Obsidian-flavored Markdown:
196
+
197
+ ### Entry Point
198
+ Start at \`docs/_index.md\`. It contains:
199
+ - The full graph map (every document listed)
200
+ - Tag taxonomy for filtering
201
+ - Traversal instructions for different tasks
202
+
203
+ ### Loading Order
204
+ When starting a task:
205
+ 1. Read \`docs/_index.md\` to orient
206
+ 2. Read the domain-specific architecture doc
207
+ 3. Read related ADRs in \`docs/decisions/\`
208
+ 4. Read the relevant runbook if performing an operational task
209
+
210
+ ### Linking Conventions
211
+ - Wikilinks: \`[[architecture/overview]]\` links to \`docs/architecture/overview.md\`
212
+ - Callouts: \`> [!decision]\`, \`> [!warning]\`, \`> [!context]\`
213
+ - Diagrams: Mermaid code blocks for system diagrams
214
+ `:"",o=`## Pre-Push Checklist
215
+
216
+ Before pushing any branch:
217
+
218
+ ${i(e,"knowledge")?`- [ ] Documentation updated for any schema, API, or env var changes
219
+ `:""}${i(e,"harness")?"- [ ] `make -f Makefile.control check` passes (lint + typecheck)\n- [ ] `make -f Makefile.control test` passes\n":`- [ ] Lint passes
220
+ - [ ] Tests pass
221
+ `}${i(e,"control")?"- [ ] `make -f Makefile.control policy-check` reviewed\n":""}${i(e,"knowledge")?"- [ ] `docs/_index.md` updated if new docs were added\n- [ ] No broken `[[wikilinks]]`\n":""}- [ ] Commit messages are clear and reference relevant context
222
+ `;return`# AGENTS.md \u2014 ${e.name} Agent Guide
223
+
224
+ This is the entry point for AI coding agents working on ${e.name}.
225
+ Read this file first. Follow the protocols exactly.
226
+
227
+ ## Quick Start
228
+
229
+ 1. **Read this file** completely before doing any work.
230
+ ${i(e,"knowledge")?"2. **Traverse the knowledge graph** starting at `docs/_index.md`.\n":""}${i(e,"control")?"3. **Check policy gates** \u2014 run `bash scripts/harness/check-policy.sh` to see if your changes trigger any policies.\n":""}4. **Implement changes** following the constraints below.
231
+ ${i(e,"knowledge")?`5. **Update documentation** \u2014 any code change that affects architecture, APIs, schemas, or env vars must have a corresponding doc update.
232
+ `:""}${i(e,"harness")?"6. **Run checks** \u2014 `make -f Makefile.control check` before committing.\n":""}
233
+
234
+ ## Repository Structure
235
+
236
+ | Path | Type | Description |
237
+ |------|------|-------------|
238
+ | \`apps/app\` | Next.js | Main application (port 3000) |
239
+ | \`apps/api\` | Next.js | API server (port 3002) |
240
+ | \`apps/web\` | Next.js | Marketing site (port 3001) |
241
+ | \`apps/docs\` | Docs | Public documentation |
242
+ | \`apps/email\` | React Email | Email templates |
243
+ | \`apps/storybook\` | Storybook | Component explorer |
244
+ | \`packages/database\` | Prisma | ORM + schema |
245
+ | \`packages/design-system\` | shadcn/ui | Shared UI components |
246
+ | \`packages/auth\` | Auth | Authentication |
247
+ ${i(e,"control")?"| `.control/` | YAML | Policy gates, commands, topology |\n":""}${i(e,"knowledge")?"| `docs/` | Markdown | Knowledge graph (Obsidian-flavored) |\n":""}${i(e,"harness")?"| `scripts/harness/` | Bash | Automation scripts |\n":""}
248
+
249
+ ${a}
250
+
251
+ ## Constraints
252
+
253
+ These are hard rules. Violations will be caught by CI or pre-commit hooks.
254
+
255
+ 1. **App Router only** \u2014 No \`pages/\` directory. All routes use Next.js App Router.
256
+ 2. **Server Components by default** \u2014 Only add \`'use client'\` when the component needs browser APIs, hooks, or event handlers.
257
+ 3. **Prisma for database** \u2014 All schema changes go through the Prisma schema.
258
+ 4. **No secrets in code** \u2014 Environment variables go in \`.env.local\` (gitignored).
259
+ 5. **Workspace imports** \u2014 Use \`@repo/<package>\` imports. Never use relative paths across package boundaries.
260
+ ${i(e,"knowledge")?"6. **Update docs on schema changes** \u2014 Any change to the database schema, API contracts, or environment variables requires a documentation update.\n7. **ADR for architectural decisions** \u2014 Major changes need an Architecture Decision Record in `docs/decisions/`.\n":""}${i(e,"control")?"8. **Policy compliance** \u2014 Check `.control/policy.yaml` for risk gates before committing high-risk changes.\n":""}9. **TypeScript strict mode** \u2014 \`noEmit\` typecheck must pass. No \`any\` types without justification.
261
+
262
+ ${s}
263
+ ${o}
264
+ `},ee={name:"consciousness",description:"Metalayer-aware CLAUDE.md and AGENTS.md for AI agent governance",dependsOn:["control","harness","knowledge"],generate:e=>[{path:"CLAUDE.md",content:Ye(e)},{path:"AGENTS.md",content:ze(e)}]};var Xe=e=>`# .control/policy.yaml \u2014 Policy gates for ${e.name}
265
+ # Each policy defines risk level, required checks, and approval conditions.
266
+
267
+ policies:
268
+ database-migration:
269
+ description: "Any change to the database schema"
270
+ risk: high
271
+ triggers:
272
+ - "packages/database/prisma/schema.prisma"
273
+ - "packages/database/prisma/migrations/**"
274
+ requires:
275
+ - adr: "Must have an ADR in docs/decisions/ explaining the schema change"
276
+ - tests: "Migration must include or update relevant tests"
277
+ - backup: "Ensure a database backup exists before applying to production"
278
+ - review: "Requires at least one reviewer with database domain knowledge"
279
+ rollback: "Use prisma migrate resolve to mark failed migrations"
280
+
281
+ dependency-update:
282
+ description: "Adding or updating dependencies in any package.json"
283
+ risk: medium
284
+ triggers:
285
+ - "**/package.json"
286
+ - "${w(e)}"
287
+ requires:
288
+ - audit: "Run audit to check for vulnerabilities"
289
+ - build: "Full build must pass after dependency change"
290
+ - test: "All tests must pass"
291
+ notes: "Prefer exact versions. Document why new dependencies are needed."
292
+
293
+ env-variable-change:
294
+ description: "Adding or modifying environment variables"
295
+ risk: high
296
+ triggers:
297
+ - "**/.env.example"
298
+ - "**/.env.local.example"
299
+ - "**/env.ts"
300
+ - "**/keys.ts"
301
+ requires:
302
+ - docs: "Update docs/schemas/env-variables.md with the new variable"
303
+ - secrets: "Never commit actual secret values"
304
+ - validation: "Add Zod validation in the relevant env.ts file"
305
+ notes: "All env vars must be documented and validated at startup."
306
+
307
+ new-package:
308
+ description: "Creating a new workspace package under packages/ or apps/"
309
+ risk: medium
310
+ triggers:
311
+ - "packages/*/package.json"
312
+ - "apps/*/package.json"
313
+ requires:
314
+ - topology: "Update .control/topology.yaml with the new package"
315
+ - docs: "Add architecture doc in docs/architecture/"
316
+ - index: "Update docs/_index.md to reference the new doc"
317
+ notes: "Follow existing package conventions. Use @repo/ scope."
318
+
319
+ pr-feedback-loop:
320
+ description: "PR review comments must be resolved before merge"
321
+ risk: medium
322
+ triggers:
323
+ - manual
324
+ requires:
325
+ - triage: "Every review comment must be triaged: accept, fix, or reject with rationale"
326
+ - commit: "Fixes must be committed and pushed (not amended into prior commits)"
327
+ - ci: "All CI checks must pass after fixes are applied"
328
+ - reply: "Each resolved comment must have a reply confirming the resolution"
329
+ notes: "PR comments are sensors in the control loop. Unresolved comments are entropy \u2014 reduce them to zero before merge."
330
+
331
+ production-deploy:
332
+ description: "Deploying to production"
333
+ risk: critical
334
+ triggers:
335
+ - manual
336
+ requires:
337
+ - ci: "All CI checks must pass (lint, typecheck, test, build)"
338
+ - review: "At least one approved review on the PR"
339
+ - docs: "All documentation must be up to date"
340
+ - policy: "All policy gates for included changes must be satisfied"
341
+ - staging: "Changes should be verified in preview deployment first"
342
+ rollback: "Use hosting provider's rollback to previous deployment"
343
+ `,Qe=e=>{let t=e.packageManager;return`# .control/commands.yaml \u2014 Canonical commands for ${e.name}
344
+ # Each command maps to a harness script in scripts/harness/.
345
+
346
+ commands:
347
+ smoke:
348
+ description: "Quick validation: install, check, build the main app"
349
+ script: "scripts/harness/smoke.sh"
350
+ timeout: 120
351
+ usage: "make -f Makefile.control smoke"
352
+ when: "Before starting work, after pulling changes"
353
+
354
+ check:
355
+ description: "Lint + typecheck"
356
+ script: "scripts/harness/check.sh"
357
+ timeout: 60
358
+ usage: "make -f Makefile.control check"
359
+ when: "Before committing, after any code change"
360
+
361
+ test:
362
+ description: "Run test suite"
363
+ script: "${t} run test"
364
+ timeout: 180
365
+ usage: "make -f Makefile.control test"
366
+ when: "Before pushing, after logic changes"
367
+
368
+ build:
369
+ description: "Full build"
370
+ script: "${t} run build"
371
+ timeout: 300
372
+ usage: "make -f Makefile.control build"
373
+ when: "Before deploy, to verify everything compiles"
374
+
375
+ ci:
376
+ description: "Full CI pipeline: check + test + build"
377
+ script: "scripts/harness/ci.sh"
378
+ timeout: 600
379
+ usage: "make -f Makefile.control ci"
380
+ when: "Simulating the full CI pipeline locally"
381
+
382
+ docs-check:
383
+ description: "Verify documentation freshness and index coverage"
384
+ script: "scripts/harness/check-docs-freshness.sh"
385
+ timeout: 10
386
+ usage: "make -f Makefile.control docs-check"
387
+ when: "After modifying any file in docs/"
388
+
389
+ deploy:
390
+ description: "Deploy to production"
391
+ script: "vercel --prod"
392
+ timeout: 300
393
+ usage: "make -f Makefile.control deploy"
394
+ when: "After all CI checks pass and PR is approved"
395
+ `},Ze=e=>`# .control/topology.yaml \u2014 Repository topology map for ${e.name}
396
+ # Describes all apps, packages, and their relationships.
397
+
398
+ apps:
399
+ app:
400
+ path: "apps/app"
401
+ description: "Main application (Next.js App Router)"
402
+ port: 3000
403
+ risk: high
404
+ domain: dashboard
405
+ dependencies:
406
+ - "@repo/auth"
407
+ - "@repo/database"
408
+ - "@repo/design-system"
409
+ - "@repo/observability"
410
+
411
+ api:
412
+ path: "apps/api"
413
+ description: "API server (Next.js Route Handlers)"
414
+ port: 3002
415
+ risk: high
416
+ domain: api
417
+ dependencies:
418
+ - "@repo/auth"
419
+ - "@repo/database"
420
+ - "@repo/observability"
421
+
422
+ web:
423
+ path: "apps/web"
424
+ description: "Marketing website (Next.js)"
425
+ port: 3001
426
+ risk: low
427
+ domain: marketing
428
+ dependencies:
429
+ - "@repo/design-system"
430
+
431
+ docs:
432
+ path: "apps/docs"
433
+ description: "Public documentation site"
434
+ port: 3004
435
+ risk: low
436
+ domain: docs
437
+ dependencies: []
438
+
439
+ email:
440
+ path: "apps/email"
441
+ description: "Email templates (React Email)"
442
+ port: 3003
443
+ risk: low
444
+ domain: email
445
+ dependencies:
446
+ - "@repo/email"
447
+
448
+ storybook:
449
+ path: "apps/storybook"
450
+ description: "Component library explorer (Storybook)"
451
+ port: 6006
452
+ risk: low
453
+ domain: design
454
+ dependencies:
455
+ - "@repo/design-system"
456
+
457
+ packages:
458
+ auth:
459
+ path: "packages/auth"
460
+ description: "Authentication"
461
+ domain: auth
462
+
463
+ database:
464
+ path: "packages/database"
465
+ description: "Database ORM"
466
+ domain: database
467
+ risk: high
468
+
469
+ design-system:
470
+ path: "packages/design-system"
471
+ description: "UI components"
472
+ domain: design
473
+
474
+ email:
475
+ path: "packages/email"
476
+ description: "Email sending"
477
+ domain: email
478
+
479
+ observability:
480
+ path: "packages/observability"
481
+ description: "Logging, error tracking, monitoring"
482
+ domain: observability
483
+
484
+ typescript-config:
485
+ path: "packages/typescript-config"
486
+ description: "Shared TypeScript configuration"
487
+ domain: infra
488
+
489
+ knowledge:
490
+ entry_point: "docs/_index.md"
491
+ architecture: "docs/architecture/"
492
+ decisions: "docs/decisions/"
493
+ runbooks: "docs/runbooks/"
494
+ schemas: "docs/schemas/"
495
+ `,te={name:"control",description:"Governance gates, command registry, and repo topology (.control/*.yaml)",generate:e=>[{path:".control/policy.yaml",content:Xe(e)},{path:".control/commands.yaml",content:Qe(e)},{path:".control/topology.yaml",content:Ze(e)}]};var et=e=>e==="bun"?` - name: Setup Bun
496
+ uses: oven-sh/setup-bun@v2`:e==="pnpm"?` - name: Setup pnpm
497
+ uses: pnpm/action-setup@v4
498
+ - name: Setup Node.js
499
+ uses: actions/setup-node@v4
500
+ with:
501
+ node-version: 20
502
+ cache: pnpm`:` - name: Setup Node.js
503
+ uses: actions/setup-node@v4
504
+ with:
505
+ node-version: 20
506
+ cache: ${e}`,tt=e=>`${u("smoke.sh \u2014 Quick validation: install, check, build the main app","make -f Makefile.control smoke","~120s")}
507
+ echo "=== Smoke Test ==="
508
+ echo ""
509
+
510
+ echo "[1/3] Installing dependencies (frozen lockfile)..."
511
+ ${y(e,!0)}
512
+ echo ""
513
+
514
+ echo "[2/3] Running checks (lint + typecheck)..."
515
+ ${l(e,"check")}
516
+ echo ""
517
+
518
+ echo "[3/3] Building main app..."
519
+ ${l(e,"build")} ${e.packageManager==="npm"?"--workspace app":"--filter app"}
520
+ echo ""
521
+
522
+ echo "=== Smoke test passed ==="
523
+ `,at=e=>`${u("check.sh \u2014 Lint + typecheck","make -f Makefile.control check","~60s")}
524
+ echo "=== Check: Lint + Typecheck ==="
525
+ echo ""
526
+
527
+ echo "[1/2] Running lint..."
528
+ ${l(e,"check")}
529
+ echo ""
530
+
531
+ echo "[2/2] Running TypeScript typecheck..."
532
+ ${F(e,"tsc --noEmit")}
533
+ echo ""
534
+
535
+ echo "=== All checks passed ==="
536
+ `,st=e=>`${u("ci.sh \u2014 Full CI pipeline: check + test + build","make -f Makefile.control ci","~600s")}
537
+ echo "=== CI Pipeline ==="
538
+ echo ""
539
+
540
+ echo "[1/3] Running checks (lint + typecheck)..."
541
+ bash scripts/harness/check.sh
542
+ echo ""
543
+
544
+ echo "[2/3] Running tests..."
545
+ ${l(e,"test")}
546
+ echo ""
547
+
548
+ echo "[3/3] Running full build..."
549
+ ${l(e,"build")}
550
+ echo ""
551
+
552
+ echo "=== CI pipeline passed ==="
553
+ `,ot=e=>`${u("check-policy.sh \u2014 Policy compliance checker for staged changes","make -f Makefile.control policy-check")}
554
+ echo "=== Policy Compliance Check ==="
555
+
556
+ # Get staged files (or all changed files if not in a git context)
557
+ if git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
558
+ STAGED=$(git diff --cached --name-only 2>/dev/null || git diff --name-only HEAD 2>/dev/null || true)
559
+ else
560
+ echo " Not in a git repository. Skipping policy check."
561
+ exit 0
562
+ fi
563
+
564
+ if [ -z "$STAGED" ]; then
565
+ echo " No staged files. Nothing to check."
566
+ exit 0
567
+ fi
568
+
569
+ WARNINGS=0
570
+
571
+ # Policy: database-migration
572
+ if echo "$STAGED" | grep -q "packages/database/prisma/schema.prisma\\|packages/database/prisma/migrations"; then
573
+ echo ""
574
+ echo " [HIGH RISK] Database migration detected!"
575
+ echo " - Required: ADR in docs/decisions/ explaining the change"
576
+ echo " - Required: Tests covering the migration"
577
+ echo " - Required: Database backup before production apply"
578
+ WARNINGS=$((WARNINGS + 1))
579
+ fi
580
+
581
+ # Policy: dependency-update
582
+ if echo "$STAGED" | grep -q "package.json\\|${w(e)}"; then
583
+ echo ""
584
+ echo " [MEDIUM RISK] Dependency change detected!"
585
+ echo " - Recommended: Run audit"
586
+ echo " - Required: Full build and test pass"
587
+ WARNINGS=$((WARNINGS + 1))
588
+ fi
589
+
590
+ # Policy: env-variable-change
591
+ if echo "$STAGED" | grep -qE "\\.env\\.example|\\.env\\.local\\.example|env\\.ts|keys\\.ts"; then
592
+ echo ""
593
+ echo " [HIGH RISK] Environment variable change detected!"
594
+ echo " - Required: Update docs/schemas/env-variables.md"
595
+ echo " - Required: Add Zod validation in env.ts"
596
+ echo " - WARNING: Never commit actual secret values"
597
+ WARNINGS=$((WARNINGS + 1))
598
+ fi
599
+
600
+ # Policy: new-package
601
+ if echo "$STAGED" | grep -qE "^(packages|apps)/[^/]+/package\\.json$"; then
602
+ NEW_PKGS=$(echo "$STAGED" | grep -E "^(packages|apps)/[^/]+/package\\.json$" || true)
603
+ for pkg in $NEW_PKGS; do
604
+ if ! git show HEAD:"$pkg" > /dev/null 2>&1; then
605
+ echo ""
606
+ echo " [MEDIUM RISK] New package detected: $pkg"
607
+ echo " - Required: Update .control/topology.yaml"
608
+ echo " - Required: Add architecture doc in docs/architecture/"
609
+ echo " - Required: Update docs/_index.md"
610
+ WARNINGS=$((WARNINGS + 1))
611
+ fi
612
+ done
613
+ fi
614
+
615
+ echo ""
616
+ if [ "$WARNINGS" -gt 0 ]; then
617
+ echo " Found $WARNINGS policy warning(s). Review before proceeding."
618
+ echo " (Policy checks are advisory \u2014 they do not block commits.)"
619
+ else
620
+ echo " No policy warnings."
621
+ fi
622
+
623
+ echo "=== Policy check complete ==="
624
+ `,nt=()=>`${u("check-docs-freshness.sh \u2014 Verify documentation index coverage","make -f Makefile.control docs-check")}
625
+ DOCS_DIR="$REPO_ROOT/docs"
626
+ INDEX_FILE="$DOCS_DIR/_index.md"
627
+
628
+ echo "=== Docs Freshness Check ==="
629
+
630
+ if [ ! -d "$DOCS_DIR" ]; then
631
+ echo " WARN: docs/ directory does not exist yet. Skipping."
632
+ exit 0
633
+ fi
634
+
635
+ if [ ! -f "$INDEX_FILE" ]; then
636
+ echo " WARN: docs/_index.md does not exist yet. Skipping."
637
+ exit 0
638
+ fi
639
+
640
+ MISSING=()
641
+
642
+ while IFS= read -r -d '' doc; do
643
+ rel_path="\${doc#"$DOCS_DIR"/}"
644
+
645
+ if [[ "$rel_path" == _templates/* ]]; then
646
+ continue
647
+ fi
648
+
649
+ if [[ "$rel_path" == "_index.md" ]]; then
650
+ continue
651
+ fi
652
+
653
+ basename_no_ext="\${rel_path%.md}"
654
+ if ! grep -q "$basename_no_ext" "$INDEX_FILE" && ! grep -q "$rel_path" "$INDEX_FILE"; then
655
+ MISSING+=("$rel_path")
656
+ fi
657
+ done < <(find "$DOCS_DIR" -name '*.md' -print0 | sort -z)
658
+
659
+ if [ \${#MISSING[@]} -gt 0 ]; then
660
+ echo " ERROR: The following docs are NOT referenced in docs/_index.md:"
661
+ for m in "\${MISSING[@]}"; do
662
+ echo " - $m"
663
+ done
664
+ echo ""
665
+ echo " Add references to these files in docs/_index.md."
666
+ exit 1
667
+ fi
668
+
669
+ echo " All docs are referenced in _index.md."
670
+ echo "=== Docs freshness check passed ==="
671
+ `,rt=()=>`${u("check-wikilinks.sh \u2014 Validate [[wikilinks]] in docs/*.md files","make -f Makefile.control wikilinks-check")}
672
+ DOCS_DIR="$REPO_ROOT/docs"
673
+
674
+ echo "=== Wikilink Validation ==="
675
+
676
+ if [ ! -d "$DOCS_DIR" ]; then
677
+ echo " WARN: docs/ directory does not exist yet. Skipping."
678
+ exit 0
679
+ fi
680
+
681
+ BROKEN=()
682
+ CHECKED=0
683
+
684
+ while IFS= read -r -d '' doc; do
685
+ while IFS= read -r link; do
686
+ target="\${link%%|*}"
687
+ target="\${target%%#*}"
688
+
689
+ if [ -z "$target" ]; then
690
+ continue
691
+ fi
692
+
693
+ if [[ "$target" == ...* ]]; then
694
+ continue
695
+ fi
696
+
697
+ if [[ "$target" == *NNN* ]] || [[ "$target" == *XXX* ]]; then
698
+ continue
699
+ fi
700
+
701
+ if [[ "$target" == "wikilinks" ]] || [[ "$target" == "internal-link" ]]; then
702
+ continue
703
+ fi
704
+
705
+ target="\${target%%#*}"
706
+ target="\${target%/}"
707
+
708
+ if [ -z "$target" ]; then
709
+ continue
710
+ fi
711
+
712
+ CHECKED=$((CHECKED + 1))
713
+
714
+ target_file="$DOCS_DIR/\${target}.md"
715
+ target_dir="$DOCS_DIR/\${target}"
716
+
717
+ if [ ! -f "$target_file" ] && [ ! -d "$target_dir" ]; then
718
+ rel_doc="\${doc#"$REPO_ROOT"/}"
719
+ BROKEN+=("$rel_doc -> [[\${target}]] (expected: docs/\${target}.md)")
720
+ fi
721
+ done < <(
722
+ sed '/^\`\`\`/,/^\`\`\`/d' "$doc" \\
723
+ | sed 's/\`[^\`]*\`//g' \\
724
+ | grep -oE '\\[\\[[^]]+\\]\\]' 2>/dev/null \\
725
+ | sed 's/^\\[\\[//; s/\\]\\]$//' \\
726
+ || true
727
+ )
728
+ done < <(find "$DOCS_DIR" -name '*.md' -print0 | sort -z)
729
+
730
+ echo " Checked $CHECKED wikilinks."
731
+
732
+ if [ \${#BROKEN[@]} -gt 0 ]; then
733
+ echo ""
734
+ echo " ERROR: Found \${#BROKEN[@]} broken wikilink(s):"
735
+ for b in "\${BROKEN[@]}"; do
736
+ echo " - $b"
737
+ done
738
+ echo ""
739
+ echo " Create the missing target files or fix the wikilink paths."
740
+ exit 1
741
+ fi
742
+
743
+ echo " No broken wikilinks found."
744
+ echo "=== Wikilink validation passed ==="
745
+ `,it=()=>`${u("audit.sh \u2014 Entropy audit: topology coverage, stale docs, broken wikilinks","make -f Makefile.control audit")}
746
+ echo "=== Entropy Audit ==="
747
+ echo ""
748
+ ISSUES=0
749
+
750
+ # --- 1. Topology Coverage ---
751
+ echo "[1/3] Checking topology coverage..."
752
+
753
+ TOPOLOGY_FILE="$REPO_ROOT/.control/topology.yaml"
754
+ if [ ! -f "$TOPOLOGY_FILE" ]; then
755
+ echo " ERROR: .control/topology.yaml not found!"
756
+ ISSUES=$((ISSUES + 1))
757
+ else
758
+ for app_dir in "$REPO_ROOT"/apps/*/; do
759
+ app_name=$(basename "$app_dir")
760
+ if ! grep -q "^ \${app_name}:" "$TOPOLOGY_FILE" 2>/dev/null; then
761
+ echo " WARN: apps/$app_name is not listed in .control/topology.yaml"
762
+ ISSUES=$((ISSUES + 1))
763
+ fi
764
+ done
765
+
766
+ for pkg_dir in "$REPO_ROOT"/packages/*/; do
767
+ pkg_name=$(basename "$pkg_dir")
768
+ if ! grep -q "^ \${pkg_name}:" "$TOPOLOGY_FILE" 2>/dev/null; then
769
+ echo " WARN: packages/$pkg_name is not listed in .control/topology.yaml"
770
+ ISSUES=$((ISSUES + 1))
771
+ fi
772
+ done
773
+
774
+ echo " Topology coverage check complete."
775
+ fi
776
+ echo ""
777
+
778
+ # --- 2. Stale Docs ---
779
+ echo "[2/3] Checking for stale documentation (>90 days)..."
780
+
781
+ DOCS_DIR="$REPO_ROOT/docs"
782
+ if [ ! -d "$DOCS_DIR" ]; then
783
+ echo " WARN: docs/ directory does not exist yet. Skipping stale docs check."
784
+ else
785
+ STALE_THRESHOLD=$((90 * 24 * 60 * 60))
786
+ NOW=$(date +%s)
787
+ STALE_COUNT=0
788
+
789
+ while IFS= read -r -d '' doc; do
790
+ if [[ "$OSTYPE" == "darwin"* ]]; then
791
+ MOD_TIME=$(stat -f %m "$doc")
792
+ else
793
+ MOD_TIME=$(stat -c %Y "$doc")
794
+ fi
795
+ AGE=$((NOW - MOD_TIME))
796
+
797
+ if [ "$AGE" -gt "$STALE_THRESHOLD" ]; then
798
+ rel_path="\${doc#"$REPO_ROOT"/}"
799
+ DAYS_OLD=$((AGE / 86400))
800
+ echo " STALE: $rel_path (\${DAYS_OLD} days old)"
801
+ STALE_COUNT=$((STALE_COUNT + 1))
802
+ fi
803
+ done < <(find "$DOCS_DIR" -name '*.md' -print0 2>/dev/null)
804
+
805
+ if [ "$STALE_COUNT" -eq 0 ]; then
806
+ echo " No stale docs found."
807
+ else
808
+ echo " Found $STALE_COUNT stale doc(s)."
809
+ ISSUES=$((ISSUES + STALE_COUNT))
810
+ fi
811
+ fi
812
+ echo ""
813
+
814
+ # --- 3. Broken Wikilinks ---
815
+ echo "[3/3] Checking for broken wikilinks..."
816
+ if [ -f "$REPO_ROOT/scripts/harness/check-wikilinks.sh" ]; then
817
+ if ! bash "$REPO_ROOT/scripts/harness/check-wikilinks.sh"; then
818
+ ISSUES=$((ISSUES + 1))
819
+ fi
820
+ else
821
+ echo " WARN: check-wikilinks.sh not found. Skipping."
822
+ fi
823
+
824
+ echo ""
825
+ echo "=== Audit Summary ==="
826
+ if [ "$ISSUES" -gt 0 ]; then
827
+ echo " Found $ISSUES issue(s). Review and address above warnings."
828
+ exit 1
829
+ else
830
+ echo " No issues found. Repository is in good shape."
831
+ exit 0
832
+ fi
833
+ `,ct=()=>`${u("install-hooks.sh \u2014 Install git hooks","make -f Makefile.control hooks-install")}
834
+ HOOKS_DIR="$REPO_ROOT/.git/hooks"
835
+
836
+ if [ ! -d "$REPO_ROOT/.git" ]; then
837
+ echo "No .git directory found. Skipping hook installation."
838
+ exit 0
839
+ fi
840
+
841
+ mkdir -p "$HOOKS_DIR"
842
+
843
+ cp "$REPO_ROOT/scripts/harness/pre-commit.sh" "$HOOKS_DIR/pre-commit"
844
+ chmod +x "$HOOKS_DIR/pre-commit"
845
+
846
+ echo "Git hooks installed:"
847
+ echo " - pre-commit -> scripts/harness/pre-commit.sh"
848
+ `,lt=e=>`${u("pre-commit.sh \u2014 Fast pre-commit hook (<10s)","Installed as .git/hooks/pre-commit via install-hooks.sh")}
849
+ echo "=== Pre-commit Hook ==="
850
+
851
+ STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\\.(ts|tsx)$' || true)
852
+
853
+ if [ -n "$STAGED_FILES" ]; then
854
+ echo "[1/2] Linting staged TypeScript files..."
855
+ echo "$STAGED_FILES" | xargs ${F(e,"biome check --no-errors-on-unmatched")}
856
+ else
857
+ echo "[1/2] No staged TypeScript files to lint."
858
+ fi
859
+
860
+ echo "[2/2] Checking docs freshness..."
861
+ if [ -f "$REPO_ROOT/scripts/harness/check-docs-freshness.sh" ]; then
862
+ bash "$REPO_ROOT/scripts/harness/check-docs-freshness.sh"
863
+ else
864
+ echo " Skipping: check-docs-freshness.sh not found."
865
+ fi
866
+
867
+ echo "=== Pre-commit passed ==="
868
+ `,pt=e=>`# Makefile.control \u2014 Control harness for ${e.name}
869
+ # Usage: make -f Makefile.control <target>
870
+ # All targets delegate to scripts in scripts/harness/
871
+
872
+ .PHONY: help smoke check test build ci docs-check wikilinks-check policy-check hooks-install audit
873
+
874
+ SHELL := /usr/bin/env bash
875
+
876
+ help: ## Show all available targets
877
+ @echo "${e.name} \u2014 Control Harness"
878
+ @echo ""
879
+ @echo "Usage: make -f Makefile.control <target>"
880
+ @echo ""
881
+ @echo "Targets:"
882
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " \\033[36m%-18s\\033[0m %s\\n", $$1, $$2}'
883
+
884
+ smoke: ## Quick validation: install + check + build app (~120s)
885
+ @bash scripts/harness/smoke.sh
886
+
887
+ check: ## Lint + typecheck (~60s)
888
+ @bash scripts/harness/check.sh
889
+
890
+ test: ## Run test suite (~180s)
891
+ @${l(e,"test")}
892
+
893
+ build: ## Full build (~300s)
894
+ @${l(e,"build")}
895
+
896
+ ci: ## Full CI pipeline: check + test + build (~600s)
897
+ @bash scripts/harness/ci.sh
898
+
899
+ docs-check: ## Verify documentation freshness and index coverage (~10s)
900
+ @bash scripts/harness/check-docs-freshness.sh
901
+
902
+ wikilinks-check: ## Validate no broken [[wikilinks]] in docs/ (~10s)
903
+ @bash scripts/harness/check-wikilinks.sh
904
+
905
+ policy-check: ## Check staged files against policy gates
906
+ @bash scripts/harness/check-policy.sh
907
+
908
+ hooks-install: ## Install git pre-commit hooks
909
+ @bash scripts/harness/install-hooks.sh
910
+
911
+ audit: ## Entropy audit: topology, stale docs, broken links
912
+ @bash scripts/harness/audit.sh
913
+ `,dt=e=>{let t=e.packageManager,a=y(e,!0);return`name: CI
914
+
915
+ on:
916
+ push:
917
+ branches: [main]
918
+ pull_request:
919
+ branches: [main]
920
+
921
+ jobs:
922
+ ci:
923
+ name: Check, Test, Build
924
+ runs-on: ubuntu-latest
925
+ steps:
926
+ - name: Checkout
927
+ uses: actions/checkout@v4
928
+
929
+ ${et(t)}
930
+
931
+ - name: Install dependencies
932
+ run: ${a}
933
+
934
+ - name: Lint + Typecheck
935
+ run: bash scripts/harness/check.sh
936
+
937
+ - name: Test
938
+ run: ${l(e,"test")}
939
+
940
+ - name: Build
941
+ run: ${l(e,"build")}
942
+ `},ae={name:"harness",description:"Build automation scripts, Makefile.control, git hooks, CI workflow",dependsOn:["control"],generate:e=>[{path:"scripts/harness/smoke.sh",content:tt(e),executable:!0},{path:"scripts/harness/check.sh",content:at(e),executable:!0},{path:"scripts/harness/ci.sh",content:st(e),executable:!0},{path:"scripts/harness/check-policy.sh",content:ot(e),executable:!0},{path:"scripts/harness/check-docs-freshness.sh",content:nt(),executable:!0},{path:"scripts/harness/check-wikilinks.sh",content:rt(),executable:!0},{path:"scripts/harness/audit.sh",content:it(),executable:!0},{path:"scripts/harness/install-hooks.sh",content:ct(),executable:!0},{path:"scripts/harness/pre-commit.sh",content:lt(e),executable:!0},{path:"Makefile.control",content:pt(e)},{path:".github/workflows/ci.yml",content:dt(e)}]};var mt=e=>{switch(e){case"bun":return"Bun";case"pnpm":return"pnpm";case"yarn":return"Yarn";default:return"npm"}},ht=e=>`---
943
+ title: "${e.name} Knowledge Graph"
944
+ type: index
945
+ domain: all
946
+ status: active
947
+ tags:
948
+ - domain/all
949
+ - status/active
950
+ - type/index
951
+ ---
952
+
953
+ # ${e.name} Knowledge Graph
954
+
955
+ > [!context]
956
+ > This is the entry point for the ${e.name} knowledge system. Every agent session should start here to understand the codebase topology, conventions, and decision history before making changes.
957
+
958
+ ## Graph Map
959
+
960
+ \`\`\`mermaid
961
+ graph TD
962
+ INDEX["_index.md<br/>(you are here)"]
963
+ GLOSS["glossary.md"]
964
+
965
+ subgraph Architecture
966
+ ARCH_OV["architecture/overview.md"]
967
+ end
968
+
969
+ subgraph Decisions
970
+ ADR1["decisions/adr-001-metalayer.md"]
971
+ end
972
+
973
+ subgraph Runbooks
974
+ RB_DEV["runbooks/local-dev-setup.md"]
975
+ end
976
+
977
+ INDEX --> GLOSS
978
+ INDEX --> ARCH_OV
979
+ INDEX --> ADR1
980
+ INDEX --> RB_DEV
981
+
982
+ ARCH_OV --> GLOSS
983
+ \`\`\`
984
+
985
+ ## Tag Taxonomy
986
+
987
+ | Prefix | Values | Purpose |
988
+ |--------|--------|---------|
989
+ | \`domain/\` | project-specific domains | Which subsystem the note covers |
990
+ | \`status/\` | \`draft\`, \`active\`, \`deprecated\` | Note lifecycle |
991
+ | \`type/\` | \`architecture\`, \`api-contract\`, \`decision\`, \`runbook\`, \`schema\`, \`index\`, \`glossary\`, \`template\` | Document type |
992
+
993
+ ## Traversal Instructions
994
+
995
+ 1. **Start here** \u2014 read this index to orient yourself in the knowledge graph
996
+ 2. **Understand the system** \u2014 read [[architecture/overview]] and [[glossary]]
997
+ 3. **Find the relevant subsystem** \u2014 follow links to specific architecture notes
998
+ 4. **Check for decisions** \u2014 before proposing alternatives, read relevant ADRs
999
+ 5. **Follow runbooks** \u2014 for operational tasks, use the runbook for that workflow
1000
+ 6. **Use templates** \u2014 when creating new docs, copy from the \`_templates/\` directory
1001
+
1002
+ ## Document Registry
1003
+
1004
+ ### Architecture
1005
+ - [[architecture/overview]] \u2014 System architecture overview
1006
+
1007
+ ### Decisions
1008
+ - [[decisions/adr-001-metalayer]] \u2014 Why the control metalayer pattern
1009
+
1010
+ ### Runbooks
1011
+ - [[runbooks/local-dev-setup]] \u2014 Getting started from scratch
1012
+
1013
+ ### Glossary
1014
+ - [[glossary]] \u2014 Key terms and definitions
1015
+ `,gt=e=>`---
1016
+ title: "Glossary"
1017
+ type: glossary
1018
+ domain: all
1019
+ status: active
1020
+ tags:
1021
+ - domain/all
1022
+ - status/active
1023
+ - type/glossary
1024
+ ---
1025
+
1026
+ # Glossary
1027
+
1028
+ ## Control Metalayer
1029
+ The governance layer (\`.control/\`) that defines policies, commands, and topology for the project. Acts as a declarative control system for development practices.
1030
+
1031
+ ## Entropy Audit
1032
+ A check that measures "drift" in the repository: uncovered topology, stale docs, broken wikilinks. Run via \`make -f Makefile.control audit\`.
1033
+
1034
+ ## Harness
1035
+ The collection of bash scripts in \`scripts/harness/\` that automate common development tasks (check, test, build, audit).
1036
+
1037
+ ## Knowledge Graph
1038
+ The interconnected documentation in \`docs/\` using Obsidian-flavored Markdown with wikilinks (\`[[target]]\`) and frontmatter tags.
1039
+
1040
+ ## Policy Gate
1041
+ A rule in \`.control/policy.yaml\` that identifies high-risk changes and their required checks before proceeding.
1042
+
1043
+ ## Topology
1044
+ The map of all apps and packages in \`.control/topology.yaml\`, including their paths, dependencies, risk levels, and domains.
1045
+
1046
+ ## Wikilink
1047
+ An Obsidian-style internal link: \`[[path/to/doc]]\` resolves to \`docs/path/to/doc.md\`.
1048
+ `,ut=e=>`---
1049
+ title: "Architecture Overview"
1050
+ type: architecture
1051
+ domain: all
1052
+ status: active
1053
+ tags:
1054
+ - domain/all
1055
+ - status/active
1056
+ - type/architecture
1057
+ ---
1058
+
1059
+ # Architecture Overview
1060
+
1061
+ > [!context]
1062
+ > High-level architecture of ${e.name}. This document describes the system topology, key components, and how they interact.
1063
+
1064
+ ## System Diagram
1065
+
1066
+ \`\`\`mermaid
1067
+ graph TD
1068
+ subgraph Apps
1069
+ APP["app (dashboard)"]
1070
+ API["api (server)"]
1071
+ WEB["web (marketing)"]
1072
+ end
1073
+
1074
+ subgraph Packages
1075
+ AUTH["auth"]
1076
+ DB["database"]
1077
+ UI["design-system"]
1078
+ OBS["observability"]
1079
+ end
1080
+
1081
+ APP --> AUTH
1082
+ APP --> DB
1083
+ APP --> UI
1084
+ APP --> OBS
1085
+ API --> AUTH
1086
+ API --> DB
1087
+ API --> OBS
1088
+ WEB --> UI
1089
+ \`\`\`
1090
+
1091
+ ## Apps
1092
+
1093
+ | App | Port | Description |
1094
+ |-----|------|-------------|
1095
+ | \`apps/app\` | 3000 | Main dashboard application |
1096
+ | \`apps/api\` | 3002 | API server |
1097
+ | \`apps/web\` | 3001 | Marketing website |
1098
+ | \`apps/docs\` | 3004 | Public documentation |
1099
+ | \`apps/email\` | 3003 | Email templates |
1100
+ | \`apps/storybook\` | 6006 | Component explorer |
1101
+
1102
+ ## Packages
1103
+
1104
+ See \`.control/topology.yaml\` for the full package map with dependencies.
1105
+
1106
+ ## Related
1107
+
1108
+ - [[glossary]]
1109
+ `,ft=e=>`---
1110
+ title: "ADR-001: Control Metalayer Pattern"
1111
+ type: decision
1112
+ domain: all
1113
+ status: active
1114
+ tags:
1115
+ - domain/all
1116
+ - status/active
1117
+ - type/decision
1118
+ ---
1119
+
1120
+ # ADR-001: Control Metalayer Pattern
1121
+
1122
+ ## Status
1123
+
1124
+ **Accepted** \u2014 scaffolded by symphony-forge
1125
+
1126
+ ## Context
1127
+
1128
+ ${e.name} needs a governance layer that:
1129
+ - Defines risk policies for high-impact changes (database migrations, env vars, deploys)
1130
+ - Provides a canonical command registry for build automation
1131
+ - Maps the full repository topology for discoverability
1132
+ - Enables AI agents to operate autonomously with safety guardrails
1133
+
1134
+ ## Decision
1135
+
1136
+ > [!decision]
1137
+ > We adopt the **control metalayer** pattern: \`.control/\` YAML for governance, \`scripts/harness/\` for automation, \`docs/\` for knowledge, and metalayer-aware agent instructions.
1138
+
1139
+ ## Rationale
1140
+
1141
+ The metalayer is a **control system** applied to development:
1142
+ - **Policy gates** are sensors that detect high-risk changes
1143
+ - **Harness scripts** are actuators that enforce standards
1144
+ - **Knowledge graph** is the system's model of itself
1145
+ - **Agent instructions** close the loop between measurement and action
1146
+
1147
+ ## Consequences
1148
+
1149
+ ### Positive
1150
+ - Agents can operate autonomously with clear guardrails
1151
+ - New contributors discover conventions via docs, not tribal knowledge
1152
+ - Entropy is measurable and auditable
1153
+
1154
+ ### Negative
1155
+ - Additional files to maintain (mitigated by audit scripts)
1156
+ - Learning curve for the metalayer conventions
1157
+
1158
+ ## Related
1159
+
1160
+ - [[architecture/overview]]
1161
+ - [[glossary]]
1162
+ `,kt=e=>{let t=e.packageManager;return`---
1163
+ title: "Runbook: Local Development Setup"
1164
+ type: runbook
1165
+ domain: all
1166
+ status: active
1167
+ tags:
1168
+ - domain/all
1169
+ - status/active
1170
+ - type/runbook
1171
+ ---
1172
+
1173
+ # Runbook: Local Development Setup
1174
+
1175
+ > [!context]
1176
+ > How to set up ${e.name} for local development from scratch.
1177
+
1178
+ ## Pre-Flight Checklist
1179
+
1180
+ - [ ] Node.js >= 18 installed
1181
+ - [ ] ${mt(t)} installed
1182
+ - [ ] Git installed
1183
+
1184
+ ## Steps
1185
+
1186
+ ### 1. Clone the repository
1187
+
1188
+ \`\`\`bash
1189
+ git clone <repository-url>
1190
+ cd ${e.name}
1191
+ \`\`\`
1192
+
1193
+ ### 2. Install dependencies
1194
+
1195
+ \`\`\`bash
1196
+ ${y(e)}
1197
+ \`\`\`
1198
+
1199
+ ### 3. Set up environment variables
1200
+
1201
+ \`\`\`bash
1202
+ # Copy example env files
1203
+ cp apps/app/.env.example apps/app/.env.local
1204
+ cp apps/api/.env.example apps/api/.env.local
1205
+ cp apps/web/.env.example apps/web/.env.local
1206
+ \`\`\`
1207
+
1208
+ ### 4. Start development server
1209
+
1210
+ \`\`\`bash
1211
+ ${l(e,"dev")}
1212
+ \`\`\`
1213
+
1214
+ ### 5. Verify setup
1215
+
1216
+ \`\`\`bash
1217
+ make -f Makefile.control smoke
1218
+ \`\`\`
1219
+
1220
+ ## Verification
1221
+
1222
+ | Check | Expected Result |
1223
+ |-------|-----------------|
1224
+ | \`make -f Makefile.control check\` | Lint + typecheck pass |
1225
+ | \`localhost:3000\` | Dashboard loads |
1226
+ | \`localhost:3001\` | Marketing site loads |
1227
+
1228
+ ## Related
1229
+
1230
+ - [[architecture/overview]]
1231
+ - [[glossary]]
1232
+ `},yt=()=>`---
1233
+ title: "ADR-NNN: [Decision Title]"
1234
+ type: decision
1235
+ domain: # specify domain
1236
+ status: draft
1237
+ tags:
1238
+ - domain/{area}
1239
+ - status/draft
1240
+ - type/decision
1241
+ ---
1242
+
1243
+ # ADR-NNN: [Decision Title]
1244
+
1245
+ ## Status
1246
+
1247
+ **Proposed** \u2014 YYYY-MM
1248
+
1249
+ ## Context
1250
+
1251
+ <!-- What is the issue that motivates this decision? -->
1252
+
1253
+ ## Options Considered
1254
+
1255
+ ### Option A: [Name]
1256
+
1257
+ <!-- Description, pros, cons -->
1258
+
1259
+ ### Option B: [Name]
1260
+
1261
+ <!-- Description, pros, cons -->
1262
+
1263
+ ## Decision
1264
+
1265
+ > [!decision]
1266
+ > We will use **[chosen option]** because [one-line rationale].
1267
+
1268
+ ## Rationale
1269
+
1270
+ <!-- Detailed reasoning -->
1271
+
1272
+ ## Consequences
1273
+
1274
+ ### Positive
1275
+ <!-- What becomes easier? -->
1276
+
1277
+ ### Negative
1278
+ <!-- What becomes harder? -->
1279
+
1280
+ ## Related
1281
+
1282
+ - [[architecture/overview]]
1283
+ `,bt=()=>`---
1284
+ title: "[Component/System Name]"
1285
+ type: architecture
1286
+ domain: # specify domain
1287
+ status: draft
1288
+ tags:
1289
+ - domain/{area}
1290
+ - status/draft
1291
+ - type/architecture
1292
+ ---
1293
+
1294
+ # [Component/System Name]
1295
+
1296
+ > [!context]
1297
+ > Brief description of what this component/system does and why it exists.
1298
+
1299
+ ## Overview
1300
+
1301
+ <!-- High-level description -->
1302
+
1303
+ ## Architecture Diagram
1304
+
1305
+ \`\`\`mermaid
1306
+ graph TD
1307
+ A["Component A"] --> B["Component B"]
1308
+ \`\`\`
1309
+
1310
+ ## Key Components
1311
+
1312
+ | Component | File/Path | Purpose |
1313
+ |-----------|-----------|---------|
1314
+ | | | |
1315
+
1316
+ ## Dependencies
1317
+
1318
+ | Dependency | Usage |
1319
+ |------------|-------|
1320
+ | | |
1321
+
1322
+ ## Related
1323
+
1324
+ - [[architecture/overview]]
1325
+ `,vt=()=>`---
1326
+ title: "Runbook: [Task Name]"
1327
+ type: runbook
1328
+ domain: # specify domain
1329
+ status: draft
1330
+ tags:
1331
+ - domain/{area}
1332
+ - status/draft
1333
+ - type/runbook
1334
+ ---
1335
+
1336
+ # Runbook: [Task Name]
1337
+
1338
+ > [!context]
1339
+ > Brief description of what this runbook covers and when to use it.
1340
+
1341
+ ## Pre-Flight Checklist
1342
+
1343
+ - [ ] Prerequisite 1
1344
+ - [ ] Prerequisite 2
1345
+
1346
+ ## Steps
1347
+
1348
+ ### 1. [First Step]
1349
+
1350
+ \`\`\`bash
1351
+ # Command
1352
+ \`\`\`
1353
+
1354
+ ### 2. [Second Step]
1355
+
1356
+ \`\`\`bash
1357
+ # Command
1358
+ \`\`\`
1359
+
1360
+ ## Verification
1361
+
1362
+ | Check | Expected Result |
1363
+ |-------|-----------------|
1364
+ | | |
1365
+
1366
+ ## Rollback
1367
+
1368
+ > [!warning]
1369
+ > Describe any destructive steps and their rollback procedures.
1370
+
1371
+ ## Related
1372
+
1373
+ - [[runbooks/local-dev-setup]]
1374
+ `,wt=()=>`---
1375
+ title: "[API Name]"
1376
+ type: api-contract
1377
+ domain: # specify domain
1378
+ status: draft
1379
+ tags:
1380
+ - domain/{area}
1381
+ - status/draft
1382
+ - type/api-contract
1383
+ ---
1384
+
1385
+ # [API Name]
1386
+
1387
+ > [!context]
1388
+ > Brief description of this API.
1389
+
1390
+ ## Base URL
1391
+
1392
+ <!-- Protocol, host, port, base path -->
1393
+
1394
+ ## Authentication
1395
+
1396
+ <!-- How are requests authenticated? -->
1397
+
1398
+ ## Endpoints
1399
+
1400
+ ### [METHOD] /path
1401
+
1402
+ **Description**: What this endpoint does.
1403
+
1404
+ **Request Body**:
1405
+
1406
+ \`\`\`typescript
1407
+ interface RequestBody {
1408
+ field: string;
1409
+ }
1410
+ \`\`\`
1411
+
1412
+ **Response**: \`200 OK\`
1413
+
1414
+ \`\`\`typescript
1415
+ interface Response {
1416
+ field: string;
1417
+ }
1418
+ \`\`\`
1419
+
1420
+ ## Related
1421
+
1422
+ - [[architecture/overview]]
1423
+ `,$t=()=>`---
1424
+ title: "[Schema Name]"
1425
+ type: schema
1426
+ domain: # specify domain
1427
+ status: draft
1428
+ tags:
1429
+ - domain/{area}
1430
+ - status/draft
1431
+ - type/schema
1432
+ ---
1433
+
1434
+ # [Schema Name]
1435
+
1436
+ > [!context]
1437
+ > Brief description of what this schema defines.
1438
+
1439
+ ## Schema Definition
1440
+
1441
+ \`\`\`
1442
+ # Schema definition here
1443
+ \`\`\`
1444
+
1445
+ ## Field Reference
1446
+
1447
+ | Field | Type | Constraints | Description |
1448
+ |-------|------|-------------|-------------|
1449
+ | | | | |
1450
+
1451
+ ## Related
1452
+
1453
+ - [[architecture/overview]]
1454
+ `,se={name:"knowledge",description:"Obsidian knowledge graph skeleton (docs/, templates, glossary, ADR)",dependsOn:["control"],generate:e=>[{path:"docs/_index.md",content:ht(e)},{path:"docs/glossary.md",content:gt(e)},{path:"docs/architecture/overview.md",content:ut(e)},{path:"docs/decisions/adr-001-metalayer.md",content:ft(e)},{path:"docs/runbooks/local-dev-setup.md",content:kt(e)},{path:"docs/_templates/adr.md",content:yt()},{path:"docs/_templates/architecture-note.md",content:bt()},{path:"docs/_templates/runbook.md",content:vt()},{path:"docs/_templates/api-contract.md",content:wt()},{path:"docs/_templates/schema-note.md",content:$t()}]};var A={control:te,harness:ae,knowledge:se,consciousness:ee,autoany:Z},I=e=>["control","harness","knowledge","consciousness","autoany"].filter(a=>e.includes(a)).map(a=>A[a]),f=Object.keys(A);import{mkdir as St,readFile as Rt,writeFile as oe}from"fs/promises";import{dirname as Ct,join as ne}from"path";import{log as Pt,spinner as At}from"@clack/prompts";var xt=async(e,t)=>{let a=[];for(let s of t){let o=ne(e,s.path);await St(Ct(o),{recursive:!0}),await oe(o,s.content,{mode:s.executable?493:420}),a.push(s.path)}return a},Et=async(e,t)=>{let a=ne(e,".symphony-forge.json"),s;try{let o=await Rt(a,"utf-8"),n=JSON.parse(o);s={...n,installedLayers:[...new Set([...n.installedLayers,...t.layers])],updatedAt:new Date().toISOString()}}catch{s={version:"1.0.0",installedLayers:t.layers,packageManager:t.packageManager,createdAt:new Date().toISOString(),updatedAt:new Date().toISOString()}}await oe(a,`${JSON.stringify(s,null,2)}
1455
+ `)},G=async(e,t)=>{let a=I(t.layers),s=[];for(let n of a){let r=n.generate(t);s.push(...r)}let o=await xt(e,s);for(let n of a)n.postInstall&&await n.postInstall(t,e);return await Et(e,t),o},re=async(e,t,a)=>{if(a){Pt.info("Skipping metalayer (--no-layers). Pure next-forge project.");return}let s=At();s.start("Scaffolding metalayer...");let o=t.layers.length>0?t.layers:f,n={...t,layers:o},r=await G(e,n);s.stop(`Metalayer scaffolded (${r.length} files across ${o.length} layers)`)};var jt=async e=>{let t=U(e,".symphony-forge.json");try{let o=await ie(t,"utf-8"),n=JSON.parse(o);return{packageManager:n.packageManager,installedLayers:n.installedLayers}}catch{}return{packageManager:(await _t(e))?.name??"npm",installedLayers:[]}},Lt=(e,t,a)=>{let s=I(t),o=[];for(let n of s){let r=n.generate(a);for(let c of r)Mt(U(e,c.path))&&o.push(c.path)}return o},Ft=async e=>{if(e==="all")return[...f];if(e&&e in A)return[e];e&&($.error(`Unknown layer: ${e}
1456
+ Available layers: ${f.join(", ")}, all`),process.exit(1));let t=await Nt({message:"Which layer would you like to add?",options:[{value:"all",label:"all \u2014 Install all layers"},...f.map(a=>({value:a,label:`${a} \u2014 ${A[a].description}`}))]});return le(t)&&(ce("Operation cancelled."),process.exit(0)),t==="all"?[...f]:[t]},pe=async e=>{try{Ot("Symphony Forge \u2014 Layer Manager");let t=process.cwd(),a=await Ft(e.name),s=await jt(t),o=U(t,"package.json"),n="my-project";try{let p=await ie(o,"utf-8");n=JSON.parse(p).name||n}catch{}let r={name:n,description:"",packageManager:s.packageManager,layers:[...new Set([...s.installedLayers,...a])]};if(!e.force){let p=Lt(t,a,r);if(p.length>0){$.warn(`The following files already exist:
1457
+ ${p.map(R=>` - ${R}`).join(`
1458
+ `)}`);let d=await It({message:"Overwrite existing files?",initialValue:!1});(le(d)||!d)&&(ce("Operation cancelled. Use --force to overwrite."),process.exit(0))}}let c=Tt();c.start(`Installing layer(s): ${a.join(", ")}...`);let m=await G(t,r);c.stop(`Installed ${m.length} files.`),$.info(`Layers installed: ${a.join(", ")}`),$.info("Files written:");for(let p of m)$.message(` ${p}`);Dt("Layer installation complete.")}catch(t){let a=t instanceof Error?t.message:`Layer command failed: ${t}`;$.error(a),process.exit(1)}};import{copyFile as Gt,mkdir as ge,readFile as Ut,rm as ue}from"fs/promises";import{dirname as Wt,join as x}from"path";import{cancel as Bt,intro as Ht,isCancel as qt,log as Kt,outro as de,select as Vt,spinner as Jt}from"@clack/prompts";var Yt=(e,t)=>{let[a,s,o]=e.split(".").map(Number),[n,r,c]=t.split(".").map(Number);return a!==n?a-n:s!==r?s-r:o-c},zt=async e=>{let t=process.cwd(),a=x(t,e);await ue(a,{recursive:!0,force:!0}),await ge(a,{recursive:!0})},Xt=e=>{g("git",["clone",P,e])},me=e=>(g("git",["checkout",e]),g("git",["ls-files"],{stdio:"pipe"}).stdout.toString().trim().split(`
1459
+ `)),Qt=async e=>{let t=process.cwd(),a=x(t,v);for(let s of e){let o=x(a,s),n=x(t,s);await ge(Wt(n),{recursive:!0}),await Gt(o,n)}},Zt=async()=>await ue(v,{recursive:!0,force:!0}),ea=async()=>{let e=x(process.cwd(),"package.json"),t=await Ut(e,"utf-8");return JSON.parse(t).version},he=async(e,t,a)=>{let s=await Vt({message:`Select a version to update ${e}:`,options:t.map(o=>({value:o,label:`v${o}`})),initialValue:a,maxItems:10});return qt(s)&&(Bt("Operation cancelled."),process.exit(0)),s.toString()},ta=(e,t)=>{let a=[];for(let s of t.files){if(K.some(r=>s.startsWith(r)))continue;if(!e.files.includes(s)){a.push(s);continue}g("git",["diff",e.version,t.version,"--",q(s)],{stdio:"pipe",maxBuffer:1024*1024*1024}).stdout.toString().trim()!==""&&a.push(s)}return a},fe=async e=>{try{Ht("Let's update your next-forge project!");let t=process.cwd(),a=await V(),s=await ea();s&&!a.includes(s)&&(s=void 0);let o=e.from||await he("from",a,s);if(o===a[0]){de("You are already on the latest version!");return}let n=a.filter(ke=>Yt(ke,o)>0),[r]=n,c=e.to||await he("to",n,r),m=`v${o}`,p=`v${c}`,d=Jt();d.start(`Preparing to update from ${m} to ${p}...`),d.message("Creating temporary directory..."),await zt(v),d.message("Cloning next-forge..."),Xt(v),d.message("Moving into repository..."),process.chdir(v),d.message(`Getting files from version ${m}...`);let R=me(m);d.message(`Getting files from version ${p}...`);let O=me(p);d.message(`Computing diff between versions ${m} and ${p}...`);let W=ta({version:m,files:R},{version:p,files:O});d.message("Moving back to original directory..."),process.chdir(t),d.message(`Updating ${W.length} files...`),await Qt(W),d.message("Cleaning up..."),await Zt(),d.stop(`Successfully updated project from ${m} to ${p}!`),de("Please review and test the changes carefully.")}catch(t){let a=t instanceof Error?t.message:`${t}`;Kt.error(`Failed to update project: ${a}`),process.exit(1)}};S.name("symphony-forge").description("Scaffold next-forge projects with a composable control metalayer").version("0.1.0");S.command("init").description("Initialize a new next-forge project with metalayer").option("--name <name>","Name of the project").option("--package-manager <manager>","Package manager to use (bun, npm, yarn, pnpm)").option("--disable-git","Disable git initialization").option("--branch <branch>","Git branch to clone from").option("--no-layers","Skip metalayer scaffolding (pure next-forge)").option("--layers <layers>","Comma-separated list of layers to install (default: all)").action(async e=>{await Q(e);let t=process.cwd(),a=e.layers?e.layers.split(",").map(o=>o.trim()):f,s={name:e.name||t.split("/").pop()||"my-project",description:"",packageManager:e.packageManager||"bun",layers:a};await re(t,s,e.layers===!1)});S.command("layer [name]").description(`Add a layer to an existing project. Available: ${f.join(", ")}, all`).option("--force","Overwrite existing files without prompting").action(async(e,t)=>{await pe({name:e,force:t.force})});S.command("audit").description("Run entropy audit on the current project").action(async()=>{await H()});S.command("update").description("Update the project from one version to another").option("--from <version>","Version to update from e.g. 1.0.0").option("--to <version>","Version to update to e.g. 2.0.0").action(fe);S.parse(process.argv);