toga-ai 1.0.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 (62) hide show
  1. package/.claude/settings.json +119 -0
  2. package/.claude-plugin/marketplace.json +87 -0
  3. package/.claude-plugin/plugin.json +22 -0
  4. package/CLAUDE.md +161 -0
  5. package/README.md +72 -0
  6. package/agents/framework-pattern-checker.md +67 -0
  7. package/agents/harness-optimizer.md +102 -0
  8. package/agents/knowledge-writer.md +62 -0
  9. package/agents/php-build-resolver.md +51 -0
  10. package/agents/php-reviewer.md +51 -0
  11. package/agents/planner.md +88 -0
  12. package/agents/session-capture.md +101 -0
  13. package/agents/sql-reviewer.md +67 -0
  14. package/contexts/dev.md +43 -0
  15. package/contexts/research.md +49 -0
  16. package/contexts/review.md +37 -0
  17. package/knowledge/1.0/apps/library/INDEX.md +5 -0
  18. package/knowledge/1.0/apps/library/architecture.md +105 -0
  19. package/knowledge/1.0/apps/worker/INDEX.md +5 -0
  20. package/knowledge/1.0/apps/worker/architecture.md +223 -0
  21. package/knowledge/1.0/standards/backend-php.md +450 -0
  22. package/knowledge/2.0/apps/_underscore/INDEX.md +6 -0
  23. package/knowledge/2.0/apps/_underscore/architecture.md +183 -0
  24. package/knowledge/2.0/apps/_underscore/features/recursive-item-fulfillments.md +111 -0
  25. package/knowledge/2.0/apps/api2/INDEX.md +5 -0
  26. package/knowledge/2.0/apps/api2/architecture.md +162 -0
  27. package/knowledge/2.0/apps/worker2/INDEX.md +6 -0
  28. package/knowledge/2.0/apps/worker2/architecture.md +127 -0
  29. package/knowledge/2.0/apps/worker2/features/creating-worker-actions.md +135 -0
  30. package/knowledge/2.0/standards/backend-php.md +710 -0
  31. package/knowledge/CONVENTIONS.md +117 -0
  32. package/knowledge/INDEX.md +19 -0
  33. package/knowledge/clients/.gitkeep +0 -0
  34. package/knowledge/registry.json +7 -0
  35. package/knowledge.js +384 -0
  36. package/mcp-configs/README.md +72 -0
  37. package/mcp-configs/mcp-servers.json +23 -0
  38. package/package.json +50 -0
  39. package/rules/README.md +53 -0
  40. package/rules/common/coding-style.md +123 -0
  41. package/rules/common/git-workflow.md +72 -0
  42. package/rules/common/security.md +118 -0
  43. package/rules/common/testing.md +74 -0
  44. package/rules/php/app-framework.md +104 -0
  45. package/rules/php/underscore-framework.md +111 -0
  46. package/scripts/harness.js +605 -0
  47. package/scripts/hooks/evaluate-session.js +55 -0
  48. package/scripts/hooks/post-edit-validate.js +102 -0
  49. package/scripts/hooks/session-end.js +13 -0
  50. package/scripts/hooks/session-start.js +57 -0
  51. package/scripts/install.js +611 -0
  52. package/scripts/pre-commit +46 -0
  53. package/skills/capture/SKILL.md +294 -0
  54. package/skills/code-review/SKILL.md +140 -0
  55. package/skills/create-elastic-beanstalk/SKILL.md +217 -0
  56. package/skills/harness-audit/SKILL.md +152 -0
  57. package/skills/kickoff/SKILL.md +151 -0
  58. package/skills/php-patterns/SKILL.md +296 -0
  59. package/skills/session-resume/SKILL.md +156 -0
  60. package/skills/session-save/SKILL.md +158 -0
  61. package/skills/sync-team-skills/SKILL.md +87 -0
  62. package/sync-skills.js +71 -0
@@ -0,0 +1,117 @@
1
+ # Knowledge Base Conventions
2
+
3
+ > **Canonical spec.** The `kickoff` and `capture` skills embed this same structure.
4
+ > **Developers never hand-edit anything under `knowledge/`** — the skills create,
5
+ > update, and delete docs and keep `registry.json`, frontmatter, and `INDEX.md`
6
+ > files consistent. This file documents the rules so humans can understand the
7
+ > layout; it is not an editing surface.
8
+
9
+ ## The two axes
10
+
11
+ 1. **Framework first.** Everything app- or framework-related lives under `1.0/` or
12
+ `2.0/`. These are the two PHP frameworks:
13
+ - **1.0** — the `App_` framework; core repo **`library`**.
14
+ - **2.0** — the `_underscore` framework; core repo **`_underscore`**.
15
+ 2. **Shared vs. client.** Within a framework, app/feature/architecture knowledge
16
+ lives under `apps/<repo>/`. Anything specific to a single client lives at the top
17
+ level under `clients/<client>/` and tags its `framework` in frontmatter.
18
+
19
+ ## Folder layout
20
+
21
+ ```
22
+ knowledge/
23
+ ├── CONVENTIONS.md # this file
24
+ ├── registry.json # repo ↔ project ↔ framework ↔ role ↔ dependsOn
25
+ ├── INDEX.md # auto-generated master index
26
+ ├── 1.0/
27
+ │ ├── apps/<repo>/architecture.md + features/*.md
28
+ │ └── standards/*.md # 1.0 coding standards (App_)
29
+ ├── 2.0/
30
+ │ ├── apps/<repo>/architecture.md + features/*.md
31
+ │ └── standards/*.md # 2.0 coding standards (_underscore)
32
+ └── clients/<client>/
33
+ ├── profile.md
34
+ ├── features/*.md # client-specific feature overrides (link back to apps/)
35
+ └── workflows/*.md # client business processes
36
+ ```
37
+
38
+ **App folders are keyed by REPO name** (`worker2`, `_underscore`, `api2`, `library`),
39
+ not project name. The human-friendly project name (e.g. "Worker", "API") is stored in
40
+ `registry.json` and in each doc's `project:` frontmatter.
41
+
42
+ ## registry.json
43
+
44
+ The shared identity/topology of every repo. Maintained **only by the skills**.
45
+
46
+ ```json
47
+ { "repo": "worker2", "project": "Worker", "framework": "2.0", "role": "app", "dependsOn": [] }
48
+ ```
49
+
50
+ - `repo` — on-disk repo/folder name (the key; unique).
51
+ - `project` — logical project name.
52
+ - `framework` — `"1.0"` or `"2.0"`.
53
+ - `role` — `"core"` (the framework base every same-framework repo depends on) or `"app"`.
54
+ - `dependsOn` — additional repos this one depends on beyond the framework core
55
+ (e.g. `api2` may later depend on `apiproxy`).
56
+
57
+ **Dependency loading:** every repo implicitly depends on its framework's `core` repo
58
+ (`_underscore` for 2.0, `library` for 1.0), plus any transitive `dependsOn`.
59
+ `node knowledge.js deps --repo=<repo>` resolves the full load order.
60
+
61
+ ## Frontmatter (every doc)
62
+
63
+ ```yaml
64
+ ---
65
+ title: Creating Worker Actions
66
+ framework: "2.0" # "1.0" | "2.0"
67
+ repo: worker2 # on-disk repo (matches folder + registry); omit for pure client docs
68
+ project: Worker # logical project name (from registry)
69
+ client: shared # "shared" for app docs; a real client slug for client docs
70
+ type: feature # feature | client-feature | workflow | architecture | standard
71
+ status: active # active | draft | deprecated
72
+ updated: 2026-06-08
73
+ owners: [jcardinal]
74
+ files: # the actual on-disk source paths this doc covers (doc → code bridge)
75
+ - worker2/Worker/Clickup.php
76
+ - worker2/Controller/Index.php
77
+ related: [] # relative links to related docs
78
+ ---
79
+ ```
80
+
81
+ The `files:` list is what makes "a feature spans many files" a non-issue: the doc
82
+ declares the files it covers, so the doc indexes into the code.
83
+
84
+ ## Document types
85
+
86
+ - **architecture** — how a repo/framework works overall (one per repo). _Elevated._
87
+ - **feature** — a discrete, shared capability or repeatable workflow within a repo.
88
+ - **client-feature** — a client's customization/override of a shared feature; links
89
+ back to the shared `apps/<repo>/features/<base>.md`.
90
+ - **workflow** — a client business process.
91
+ - **standard** — a coding standard for a framework. _Elevated._
92
+
93
+ _Elevated_ docs (architecture, standard) are senior-owned; `capture` flags any change
94
+ to them with ⚠ so the developer knows what they're approving.
95
+
96
+ ## INDEX.md files
97
+
98
+ Never hand-edited. `node knowledge.js index` regenerates the master `INDEX.md`, each
99
+ `<fw>/apps/<repo>/INDEX.md`, and each `clients/<client>/INDEX.md` from frontmatter, so
100
+ they can never drift from the docs.
101
+
102
+ ## Integrity
103
+
104
+ `node knowledge.js validate` runs after every `capture` write and enforces:
105
+ registry self-consistency (unique repos, valid framework/role, resolvable `dependsOn`),
106
+ folder ↔ registry agreement, and that each doc's `framework`/`repo`/`project` match its
107
+ location and the registry. A non-zero exit means `capture` must stop and repair before
108
+ reporting success.
109
+
110
+ ## Local paths (NEVER committed)
111
+
112
+ Absolute paths to each repo are **machine-specific and private to each developer**. They
113
+ are **never** stored in this repo (`registry.json` deliberately contains no paths). Each
114
+ developer's paths live only in **their own computer's Claude memory** (`reference` entries:
115
+ `team-repo-path`, `repo-path-<repo>`), resolved lazily by the skills and asked for by repo
116
+ name only when a session actually needs them. Do not add a path field to `registry.json` or
117
+ any committed doc — if you ever find one, it is a mistake to remove.
@@ -0,0 +1,19 @@
1
+ # Knowledge Index
2
+
3
+ _Auto-generated by `knowledge.js index`. Do not hand-edit._
4
+
5
+ ## 1.0 framework
6
+
7
+ - **library** (Library) _(framework core)_ — 1 doc(s) → [1.0/apps/library/INDEX.md](1.0/apps/library/INDEX.md)
8
+ - **worker** (Worker) — 1 doc(s) → [1.0/apps/worker/INDEX.md](1.0/apps/worker/INDEX.md)
9
+
10
+ ## 2.0 framework
11
+
12
+ - **_underscore** (_Underscore) _(framework core)_ — 2 doc(s) → [2.0/apps/_underscore/INDEX.md](2.0/apps/_underscore/INDEX.md)
13
+ - **worker2** (Worker) — 2 doc(s) → [2.0/apps/worker2/INDEX.md](2.0/apps/worker2/INDEX.md)
14
+ - **api2** (API) — 1 doc(s) → [2.0/apps/api2/INDEX.md](2.0/apps/api2/INDEX.md)
15
+
16
+ ## Clients
17
+
18
+ _No client knowledge captured yet._
19
+
File without changes
@@ -0,0 +1,7 @@
1
+ [
2
+ { "repo": "_underscore", "project": "_Underscore", "framework": "2.0", "role": "core", "dependsOn": [] },
3
+ { "repo": "worker2", "project": "Worker", "framework": "2.0", "role": "app", "dependsOn": [] },
4
+ { "repo": "api2", "project": "API", "framework": "2.0", "role": "app", "dependsOn": [] },
5
+ { "repo": "library", "project": "Library", "framework": "1.0", "role": "core", "dependsOn": [] },
6
+ { "repo": "worker", "project": "Worker", "framework": "1.0", "role": "app", "dependsOn": [] }
7
+ ]
package/knowledge.js ADDED
@@ -0,0 +1,384 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * knowledge.js — deterministic CRUD + integrity guard for the team knowledge base.
5
+ *
6
+ * The `kickoff` and `capture` skills author the prose; this script keeps the
7
+ * mechanical, error-prone parts honest: searching docs, rebuilding INDEX files
8
+ * from frontmatter, resolving framework/dependency load sets, and validating
9
+ * that registry.json, the folder layout, and every doc's frontmatter agree.
10
+ *
11
+ * No external dependencies. Works on Windows and Mac. Resolves the knowledge
12
+ * directory relative to THIS file, so it can be invoked from any cwd:
13
+ *
14
+ * node /path/to/claude/knowledge.js <command> [--flags]
15
+ *
16
+ * Commands:
17
+ * search --framework= --repo= --project= --client= --file= --q=
18
+ * index
19
+ * deps --repo=<repo>
20
+ * validate
21
+ */
22
+
23
+ 'use strict';
24
+
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+
28
+ const ROOT = path.join(__dirname, 'knowledge');
29
+ const REGISTRY = path.join(ROOT, 'registry.json');
30
+ const FRAMEWORKS = ['1.0', '2.0'];
31
+ const DOC_TYPES = ['feature', 'client-feature', 'workflow', 'architecture', 'standard'];
32
+ const ELEVATED_TYPES = ['architecture', 'standard'];
33
+
34
+ /* ------------------------------------------------------------------ */
35
+ /* small utilities */
36
+ /* ------------------------------------------------------------------ */
37
+
38
+ function fail(msg) {
39
+ console.error('ERROR: ' + msg);
40
+ process.exitCode = 1;
41
+ }
42
+
43
+ function parseArgs(argv) {
44
+ const out = {};
45
+ for (const arg of argv) {
46
+ const m = arg.match(/^--([^=]+)=(.*)$/);
47
+ if (m) out[m[1]] = m[2];
48
+ else if (arg.startsWith('--')) out[arg.slice(2)] = true;
49
+ }
50
+ return out;
51
+ }
52
+
53
+ function walkMd(dir) {
54
+ const results = [];
55
+ if (!fs.existsSync(dir)) return results;
56
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
57
+ const full = path.join(dir, entry.name);
58
+ if (entry.isDirectory()) results.push(...walkMd(full));
59
+ else if (entry.isFile() && entry.name.endsWith('.md') && entry.name !== 'INDEX.md') {
60
+ results.push(full);
61
+ }
62
+ }
63
+ return results;
64
+ }
65
+
66
+ /* ------------------------------------------------------------------ */
67
+ /* minimal frontmatter parser (handles the fields we use) */
68
+ /* ------------------------------------------------------------------ */
69
+
70
+ function parseFrontmatter(content) {
71
+ if (!content.startsWith('---')) return { data: {}, body: content };
72
+ const end = content.indexOf('\n---', 3);
73
+ if (end === -1) return { data: {}, body: content };
74
+ const raw = content.slice(3, end).replace(/^\n/, '');
75
+ const body = content.slice(end + 4).replace(/^\r?\n/, '');
76
+ const data = {};
77
+ const lines = raw.split(/\r?\n/);
78
+ let i = 0;
79
+ while (i < lines.length) {
80
+ const line = lines[i];
81
+ if (!line.trim()) { i++; continue; }
82
+ const m = line.match(/^([A-Za-z0-9_]+):\s*(.*)$/);
83
+ if (!m) { i++; continue; }
84
+ const key = m[1];
85
+ let val = m[2].trim();
86
+ if (val === '') {
87
+ // possible block list on following indented "- " lines
88
+ const arr = [];
89
+ let j = i + 1;
90
+ while (j < lines.length && /^\s*-\s+/.test(lines[j])) {
91
+ arr.push(stripQuotes(lines[j].replace(/^\s*-\s+/, '').trim()));
92
+ j++;
93
+ }
94
+ data[key] = arr;
95
+ i = j;
96
+ continue;
97
+ }
98
+ if (val.startsWith('[') && val.endsWith(']')) {
99
+ const inner = val.slice(1, -1).trim();
100
+ data[key] = inner === '' ? [] : inner.split(',').map(s => stripQuotes(s.trim()));
101
+ } else {
102
+ data[key] = stripQuotes(val);
103
+ }
104
+ i++;
105
+ }
106
+ return { data, body };
107
+ }
108
+
109
+ function stripQuotes(s) {
110
+ if ((s.startsWith('"') && s.endsWith('"')) || (s.startsWith("'") && s.endsWith("'"))) {
111
+ return s.slice(1, -1);
112
+ }
113
+ return s;
114
+ }
115
+
116
+ function readDoc(file) {
117
+ const content = fs.readFileSync(file, 'utf8');
118
+ const { data, body } = parseFrontmatter(content);
119
+ return { file, rel: path.relative(ROOT, file).split(path.sep).join('/'), data, body };
120
+ }
121
+
122
+ function allDocs() {
123
+ const dirs = [];
124
+ for (const fw of FRAMEWORKS) dirs.push(path.join(ROOT, fw));
125
+ dirs.push(path.join(ROOT, 'clients'));
126
+ const files = dirs.flatMap(walkMd);
127
+ return files.map(readDoc);
128
+ }
129
+
130
+ /* ------------------------------------------------------------------ */
131
+ /* registry */
132
+ /* ------------------------------------------------------------------ */
133
+
134
+ function loadRegistry() {
135
+ if (!fs.existsSync(REGISTRY)) return [];
136
+ try {
137
+ return JSON.parse(fs.readFileSync(REGISTRY, 'utf8'));
138
+ } catch (e) {
139
+ fail('registry.json is not valid JSON: ' + e.message);
140
+ return [];
141
+ }
142
+ }
143
+
144
+ function coreReposFor(registry, framework) {
145
+ return registry.filter(r => r.framework === framework && r.role === 'core').map(r => r.repo);
146
+ }
147
+
148
+ /* ------------------------------------------------------------------ */
149
+ /* command: deps */
150
+ /* ------------------------------------------------------------------ */
151
+
152
+ function cmdDeps(args) {
153
+ const registry = loadRegistry();
154
+ const repo = args.repo;
155
+ if (!repo) return fail('deps requires --repo=<repo>');
156
+ const entry = registry.find(r => r.repo === repo);
157
+ if (!entry) return fail('repo not in registry: ' + repo);
158
+
159
+ const seen = new Set();
160
+ const order = [];
161
+ // framework core(s) first
162
+ for (const core of coreReposFor(registry, entry.framework)) {
163
+ if (!seen.has(core)) { seen.add(core); order.push(core); }
164
+ }
165
+ // transitive dependsOn, then the repo itself
166
+ const visit = (r) => {
167
+ const e = registry.find(x => x.repo === r);
168
+ if (!e) return;
169
+ for (const d of (e.dependsOn || [])) visit(d);
170
+ if (!seen.has(r)) { seen.add(r); order.push(r); }
171
+ };
172
+ visit(repo);
173
+ console.log(order.join('\n'));
174
+ }
175
+
176
+ /* ------------------------------------------------------------------ */
177
+ /* command: search */
178
+ /* ------------------------------------------------------------------ */
179
+
180
+ function cmdSearch(args) {
181
+ const docs = allDocs();
182
+ const q = args.q ? String(args.q).toLowerCase() : null;
183
+ const matches = docs.filter(d => {
184
+ if (args.framework && d.data.framework !== args.framework) return false;
185
+ if (args.repo && d.data.repo !== args.repo) return false;
186
+ if (args.project && d.data.project !== args.project) return false;
187
+ if (args.client && d.data.client !== args.client) return false;
188
+ if (args.file) {
189
+ const files = d.data.files || [];
190
+ if (!files.some(f => f.includes(args.file))) return false;
191
+ }
192
+ if (q) {
193
+ const hay = (
194
+ (d.data.title || '') + ' ' +
195
+ (d.data.files || []).join(' ') + ' ' +
196
+ d.body
197
+ ).toLowerCase();
198
+ if (!hay.includes(q)) return false;
199
+ }
200
+ return true;
201
+ });
202
+ for (const m of matches) {
203
+ console.log(m.rel + '\t' + (m.data.title || '') + '\t' + (m.data.type || ''));
204
+ }
205
+ }
206
+
207
+ /* ------------------------------------------------------------------ */
208
+ /* command: index */
209
+ /* ------------------------------------------------------------------ */
210
+
211
+ function docRow(d) {
212
+ const title = d.data.title || path.basename(d.file, '.md');
213
+ const desc = firstSentence(d.body);
214
+ const files = (d.data.files || []).join(', ');
215
+ return `| [${title}](${path.basename(path.dirname(d.file)) === '' ? d.rel : './' + path.relative(path.dirname(d.file), d.file).split(path.sep).join('/')}) | ${desc} | ${files} |`;
216
+ }
217
+
218
+ function firstSentence(body) {
219
+ const text = body.replace(/^#.*$/gm, '').replace(/\s+/g, ' ').trim();
220
+ if (!text) return '';
221
+ const cut = text.slice(0, 160);
222
+ const dot = cut.indexOf('. ');
223
+ return (dot > 0 ? cut.slice(0, dot + 1) : cut).trim();
224
+ }
225
+
226
+ function writeIndex(file, lines) {
227
+ fs.writeFileSync(file, lines.join('\n') + '\n');
228
+ }
229
+
230
+ function cmdIndex() {
231
+ const registry = loadRegistry();
232
+ const docs = allDocs();
233
+
234
+ // per-repo INDEX.md
235
+ for (const fw of FRAMEWORKS) {
236
+ const appsDir = path.join(ROOT, fw, 'apps');
237
+ if (!fs.existsSync(appsDir)) continue;
238
+ for (const repo of fs.readdirSync(appsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name)) {
239
+ const repoDir = path.join(appsDir, repo);
240
+ const repoDocs = docs.filter(d => d.file.startsWith(repoDir + path.sep));
241
+ const entry = registry.find(r => r.repo === repo && r.framework === fw);
242
+ const lines = [
243
+ `# ${repo} (${entry ? entry.project : '?'}) — ${fw} knowledge`,
244
+ '',
245
+ '| Doc | Summary | Files |',
246
+ '|-----|---------|-------|',
247
+ ...repoDocs.map(d => relRow(d, repoDir)),
248
+ ];
249
+ writeIndex(path.join(repoDir, 'INDEX.md'), lines);
250
+ }
251
+ }
252
+
253
+ // per-client INDEX.md
254
+ const clientsDir = path.join(ROOT, 'clients');
255
+ if (fs.existsSync(clientsDir)) {
256
+ for (const client of fs.readdirSync(clientsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name)) {
257
+ const clientDir = path.join(clientsDir, client);
258
+ const clientDocs = docs.filter(d => d.file.startsWith(clientDir + path.sep));
259
+ const lines = [
260
+ `# Client: ${client}`,
261
+ '',
262
+ '| Doc | Framework | Summary | Files |',
263
+ '|-----|-----------|---------|-------|',
264
+ ...clientDocs.map(d => `| [${d.data.title || path.basename(d.file, '.md')}](${relLink(d, clientDir)}) | ${d.data.framework || ''} | ${firstSentence(d.body)} | ${(d.data.files || []).join(', ')} |`),
265
+ ];
266
+ writeIndex(path.join(clientDir, 'INDEX.md'), lines);
267
+ }
268
+ }
269
+
270
+ // master INDEX.md
271
+ const master = ['# Knowledge Index', '', '_Auto-generated by `knowledge.js index`. Do not hand-edit._', ''];
272
+ for (const fw of FRAMEWORKS) {
273
+ master.push(`## ${fw} framework`, '');
274
+ const fwEntries = registry.filter(r => r.framework === fw);
275
+ if (fwEntries.length === 0) { master.push('_No repos registered._', ''); continue; }
276
+ for (const e of fwEntries) {
277
+ const count = docs.filter(d => d.data.repo === e.repo && d.data.framework === fw).length;
278
+ const role = e.role === 'core' ? ' _(framework core)_' : '';
279
+ master.push(`- **${e.repo}** (${e.project})${role} — ${count} doc(s) → [${fw}/apps/${e.repo}/INDEX.md](${fw}/apps/${e.repo}/INDEX.md)`);
280
+ }
281
+ master.push('');
282
+ }
283
+ const clientNames = fs.existsSync(clientsDir)
284
+ ? fs.readdirSync(clientsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name) : [];
285
+ master.push('## Clients', '');
286
+ if (clientNames.length === 0) master.push('_No client knowledge captured yet._', '');
287
+ else { for (const c of clientNames) master.push(`- **${c}** → [clients/${c}/INDEX.md](clients/${c}/INDEX.md)`); master.push(''); }
288
+ writeIndex(path.join(ROOT, 'INDEX.md'), master);
289
+
290
+ console.log('index rebuilt: ' + docs.length + ' doc(s) across ' + registry.length + ' registered repo(s).');
291
+ }
292
+
293
+ function relLink(d, baseDir) {
294
+ return path.relative(baseDir, d.file).split(path.sep).join('/');
295
+ }
296
+ function relRow(d, baseDir) {
297
+ return `| [${d.data.title || path.basename(d.file, '.md')}](${relLink(d, baseDir)}) | ${firstSentence(d.body)} | ${(d.data.files || []).join(', ')} |`;
298
+ }
299
+
300
+ /* ------------------------------------------------------------------ */
301
+ /* command: validate */
302
+ /* ------------------------------------------------------------------ */
303
+
304
+ function cmdValidate() {
305
+ const registry = loadRegistry();
306
+ let ok = true;
307
+ const elevated = [];
308
+
309
+ // registry self-consistency
310
+ const repoSeen = new Set();
311
+ for (const e of registry) {
312
+ if (!e.repo) { fail('registry entry missing "repo"'); ok = false; continue; }
313
+ if (repoSeen.has(e.repo)) { fail('duplicate repo in registry: ' + e.repo); ok = false; }
314
+ repoSeen.add(e.repo);
315
+ if (!FRAMEWORKS.includes(e.framework)) { fail(`repo ${e.repo}: invalid framework "${e.framework}"`); ok = false; }
316
+ if (!['core', 'app'].includes(e.role)) { fail(`repo ${e.repo}: invalid role "${e.role}"`); ok = false; }
317
+ if (!e.project) { fail(`repo ${e.repo}: missing project name`); ok = false; }
318
+ for (const d of (e.dependsOn || [])) {
319
+ if (!registry.find(x => x.repo === d)) { fail(`repo ${e.repo}: dependsOn unknown repo "${d}"`); ok = false; }
320
+ }
321
+ }
322
+
323
+ // folders ↔ registry
324
+ for (const fw of FRAMEWORKS) {
325
+ const appsDir = path.join(ROOT, fw, 'apps');
326
+ if (!fs.existsSync(appsDir)) continue;
327
+ for (const repo of fs.readdirSync(appsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name)) {
328
+ const entry = registry.find(r => r.repo === repo && r.framework === fw);
329
+ if (!entry) { fail(`folder ${fw}/apps/${repo} has no matching registry entry`); ok = false; }
330
+ }
331
+ }
332
+
333
+ // docs frontmatter
334
+ for (const d of allDocs()) {
335
+ const required = ['title', 'framework', 'type', 'status', 'project'];
336
+ for (const f of required) {
337
+ if (!d.data[f]) { fail(`${d.rel}: missing frontmatter "${f}"`); ok = false; }
338
+ }
339
+ if (d.data.type && !DOC_TYPES.includes(d.data.type)) { fail(`${d.rel}: invalid type "${d.data.type}"`); ok = false; }
340
+ const isClientDoc = d.rel.startsWith('clients/');
341
+ if (isClientDoc) {
342
+ if (!d.data.client || d.data.client === 'shared') { fail(`${d.rel}: client doc must set a real "client"`); ok = false; }
343
+ } else {
344
+ // framework/apps doc: framework + repo must match the path
345
+ const parts = d.rel.split('/'); // <fw>/apps/<repo>/... OR <fw>/standards/...
346
+ const fw = parts[0];
347
+ if (d.data.framework !== fw) { fail(`${d.rel}: frontmatter framework "${d.data.framework}" != path "${fw}"`); ok = false; }
348
+ if (parts[1] === 'apps') {
349
+ const repo = parts[2];
350
+ if (d.data.repo !== repo) { fail(`${d.rel}: frontmatter repo "${d.data.repo}" != folder "${repo}"`); ok = false; }
351
+ const entry = registry.find(r => r.repo === repo && r.framework === fw);
352
+ if (entry && d.data.project !== entry.project) { fail(`${d.rel}: project "${d.data.project}" != registry "${entry.project}"`); ok = false; }
353
+ }
354
+ }
355
+ if (ELEVATED_TYPES.includes(d.data.type)) elevated.push(d.rel);
356
+ }
357
+
358
+ if (elevated.length) {
359
+ console.log('ELEVATED (senior-owned) docs:');
360
+ for (const e of elevated) console.log(' ⚠ ' + e);
361
+ }
362
+ if (ok) console.log('validate: OK (' + registry.length + ' repos)');
363
+ else console.log('validate: FAILED — see errors above');
364
+ }
365
+
366
+ /* ------------------------------------------------------------------ */
367
+ /* dispatch */
368
+ /* ------------------------------------------------------------------ */
369
+
370
+ function main() {
371
+ const [, , cmd, ...rest] = process.argv;
372
+ const args = parseArgs(rest);
373
+ switch (cmd) {
374
+ case 'search': return cmdSearch(args);
375
+ case 'index': return cmdIndex();
376
+ case 'deps': return cmdDeps(args);
377
+ case 'validate': return cmdValidate();
378
+ default:
379
+ console.log('Usage: node knowledge.js <search|index|deps|validate> [--flags]');
380
+ process.exitCode = 1;
381
+ }
382
+ }
383
+
384
+ main();
@@ -0,0 +1,72 @@
1
+ # MCP Server Configuration
2
+
3
+ `mcp-configs/mcp-servers.json` is a template listing the MCP servers used by the TOGA team. Copy the servers you want into your project's `.mcp.json` file.
4
+
5
+ ## Available servers
6
+
7
+ ### `github` — `@modelcontextprotocol/server-github`
8
+
9
+ GitHub integration for Claude Code. Enables:
10
+ - Reading PRs and issues without leaving the editor
11
+ - Code search across repos
12
+ - Creating and reviewing PRs from Claude Code
13
+
14
+ **Setup:**
15
+ 1. Create a GitHub personal access token at https://github.com/settings/tokens
16
+ 2. Grant: `repo`, `read:org`, `read:user`
17
+ 3. Set the environment variable: `GITHUB_PERSONAL_ACCESS_TOKEN=ghp_...`
18
+ 4. Add to your project's `.mcp.json`
19
+
20
+ ### `memory` — `@modelcontextprotocol/server-memory`
21
+
22
+ Local knowledge graph that persists between Claude Code sessions. This is what makes `/session-save` and `/session-resume` work across sessions and across projects.
23
+
24
+ **Setup:** No API key or configuration needed. Data is stored in a local SQLite file. Just add the server entry to `.mcp.json`.
25
+
26
+ **Note:** The memory server stores data per machine. It does not sync between team members — each developer has their own local memory graph.
27
+
28
+ ### `context7` — `@upstash/context7-mcp`
29
+
30
+ Fetches current documentation for PHP libraries and Composer packages. Useful when working with third-party packages — gets you accurate, up-to-date API docs instead of relying on Claude's training data.
31
+
32
+ **Setup:** No API key required for basic use. Add to `.mcp.json`.
33
+
34
+ ---
35
+
36
+ ## How to add to a project
37
+
38
+ In your project root, create or edit `.mcp.json`:
39
+
40
+ ```json
41
+ {
42
+ "mcpServers": {
43
+ "memory": {
44
+ "command": "npx",
45
+ "args": ["-y", "@modelcontextprotocol/server-memory"]
46
+ },
47
+ "context7": {
48
+ "command": "npx",
49
+ "args": ["-y", "@upstash/context7-mcp"]
50
+ },
51
+ "github": {
52
+ "command": "npx",
53
+ "args": ["-y", "@modelcontextprotocol/server-github"],
54
+ "env": {
55
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
56
+ }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ The `node scripts/install.js` command copies `mcp-configs/mcp-servers.json` to `.claude/mcp-servers.example.json` in the target project as a reference. You still need to manually merge the servers you want into `.mcp.json`.
63
+
64
+ ---
65
+
66
+ ## Recommended setup per project type
67
+
68
+ **Active development projects** (worker, worker2, api2): Add `memory` + `github` + `context7`
69
+
70
+ **Read-only / archive projects**: Add `memory` only (for session resume)
71
+
72
+ **This knowledge repo itself**: Add `memory` + `github` (for PR-based knowledge contributions)
@@ -0,0 +1,23 @@
1
+ {
2
+ "_readme": "This file is a template. Copy the servers you want into your project's .mcp.json under the 'mcpServers' key. See mcp-configs/README.md for instructions.",
3
+ "mcpServers": {
4
+ "github": {
5
+ "command": "npx",
6
+ "args": ["-y", "@modelcontextprotocol/server-github"],
7
+ "env": {
8
+ "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PERSONAL_ACCESS_TOKEN}"
9
+ },
10
+ "_description": "GitHub integration: PR reviews, issue tracking, code search across repos. Set GITHUB_PERSONAL_ACCESS_TOKEN in your environment."
11
+ },
12
+ "memory": {
13
+ "command": "npx",
14
+ "args": ["-y", "@modelcontextprotocol/server-memory"],
15
+ "_description": "Cross-session knowledge graph. Enables /session-save and /session-resume to persist state between Claude Code sessions. No configuration required — data stored locally."
16
+ },
17
+ "context7": {
18
+ "command": "npx",
19
+ "args": ["-y", "@upstash/context7-mcp"],
20
+ "_description": "Up-to-date documentation for PHP libraries, Composer packages, and frameworks. Resolves library IDs and fetches current docs. No API key required for basic use."
21
+ }
22
+ }
23
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "toga-ai",
3
+ "version": "1.0.0",
4
+ "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "ai",
9
+ "knowledge-base",
10
+ "skills",
11
+ "toga-technology",
12
+ "php",
13
+ "developer-tools"
14
+ ],
15
+ "homepage": "https://togatech.com",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/TOGATechnology/claude"
19
+ },
20
+ "license": "UNLICENSED",
21
+ "private": false,
22
+ "engines": {
23
+ "node": ">=18"
24
+ },
25
+ "bin": {
26
+ "toga-ai": "scripts/install.js"
27
+ },
28
+ "scripts": {
29
+ "postinstall": "node scripts/install.js --post-install",
30
+ "validate": "node knowledge.js validate",
31
+ "index": "node knowledge.js index",
32
+ "sync": "node sync-skills.js"
33
+ },
34
+ "files": [
35
+ "agents/",
36
+ "rules/",
37
+ "skills/",
38
+ "contexts/",
39
+ "scripts/",
40
+ "mcp-configs/",
41
+ ".claude-plugin/",
42
+ ".claude/settings.json",
43
+ "knowledge/",
44
+ "knowledge.js",
45
+ "sync-skills.js",
46
+ "CLAUDE.md"
47
+ ],
48
+ "main": "knowledge.js",
49
+ "devDependencies": {}
50
+ }