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.
- package/LICENSE +173 -0
- package/README.md +138 -0
- package/dist/index.js +1459 -0
- 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);
|