toga-ai 1.0.46 → 1.0.48

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/knowledge.js CHANGED
@@ -14,10 +14,13 @@
14
14
  * node /path/to/claude/knowledge.js <command> [--flags]
15
15
  *
16
16
  * Commands:
17
- * search --framework= --repo= --project= --client= --file= --q=
17
+ * search --framework= --repo= --project= --client= --file= --q=
18
18
  * index
19
- * deps --repo=<repo>
19
+ * deps --repo=<repo>
20
20
  * validate
21
+ * manifest
22
+ * kickoff-preflight --repos=a,b,c --client=<slug> --layer=<...> --q=<...>
23
+ * publish --msg= --mirror= --branch=
21
24
  */
22
25
 
23
26
  'use strict';
@@ -170,6 +173,142 @@ function cmdManifest() {
170
173
  console.log(JSON.stringify({ repos, clients }));
171
174
  }
172
175
 
176
+ /* ------------------------------------------------------------------ */
177
+ /* command: kickoff-preflight — one JSON bundle for kickoff Steps 3+4. */
178
+ /* Given the chosen repos (+client, +layer), resolves the dependency */
179
+ /* load-set, every doc to read (with exists flags), and an ordered */
180
+ /* `reads` list — so kickoff stops running deps/search/path-probing in */
181
+ /* context. Prints minified JSON. Flags: */
182
+ /* --repos=a,b,c (required) --client=<slug> --layer=<back-end| */
183
+ /* front-end|hybrid> --q=<keywords to match feature docs> */
184
+ /* ------------------------------------------------------------------ */
185
+
186
+ function extractSection(body, heading) {
187
+ const lines = body.split(/\r?\n/);
188
+ const want = ('## ' + heading).toLowerCase();
189
+ const start = lines.findIndex(l => l.trim().toLowerCase() === want);
190
+ if (start === -1) return '';
191
+ const out = [];
192
+ for (let i = start + 1; i < lines.length; i++) {
193
+ if (/^##\s/.test(lines[i])) break;
194
+ out.push(lines[i]);
195
+ }
196
+ return out.join('\n').trim();
197
+ }
198
+
199
+ function cmdPreflight(args) {
200
+ const registry = loadRegistry();
201
+ const chosen = String(args.repos || '').split(',').map(s => s.trim()).filter(Boolean);
202
+ if (!chosen.length) {
203
+ console.log(JSON.stringify({ error: 'PREFLIGHT: --repos=<repo,repo> required' }));
204
+ process.exitCode = 1;
205
+ return;
206
+ }
207
+ const layer = String(args.layer || 'hybrid').toLowerCase();
208
+ const q = args.q ? String(args.q).toLowerCase() : null;
209
+ const unknown = chosen.filter(r => !registry.find(x => x.repo === r));
210
+
211
+ // frameworks involved (from the chosen repos)
212
+ const fwSet = new Set();
213
+ for (const r of chosen) { const e = registry.find(x => x.repo === r); if (e) fwSet.add(e.framework); }
214
+ const frameworks = [...fwSet];
215
+
216
+ // dependency load-set: framework core(s) first, then transitive dependsOn, then chosen
217
+ const seen = new Set();
218
+ const loadSet = [];
219
+ const visit = (r) => {
220
+ const e = registry.find(x => x.repo === r);
221
+ if (!e) return;
222
+ for (const d of (e.dependsOn || [])) visit(d);
223
+ if (!seen.has(r)) { seen.add(r); loadSet.push(r); }
224
+ };
225
+ for (const fw of frameworks) for (const core of coreReposFor(registry, fw)) visit(core);
226
+ for (const r of chosen) visit(r);
227
+
228
+ const docs = allDocs();
229
+ const chosenSet = new Set(chosen);
230
+ const fileInfo = (rel) => ({ path: rel, exists: fs.existsSync(path.join(ROOT, rel.split('/').join(path.sep))) });
231
+ const matchQ = (d) => !q || (((d.data.title || '') + ' ' + (d.data.files || []).join(' ') + ' ' + d.body).toLowerCase().includes(q));
232
+
233
+ // per-repo inventory. Chosen repos load FULL architecture + matched features.
234
+ // Non-chosen repos (deps, incl. framework core) get their `## Summary` inlined
235
+ // here so kickoff reads zero extra files for them — full doc lazy on demand.
236
+ const repoOut = loadSet.map(repo => {
237
+ const e = registry.find(x => x.repo === repo);
238
+ const fw = e ? e.framework : '';
239
+ const repoDir = path.join(ROOT, fw, 'apps', repo);
240
+ const isChosen = chosenSet.has(repo);
241
+ const archRel = `${fw}/apps/${repo}/architecture.md`;
242
+ const archDoc = docs.find(d => d.rel === archRel);
243
+ let features = [];
244
+ if (isChosen) {
245
+ features = docs
246
+ .filter(d => d.file.startsWith(repoDir + path.sep) && d.data.type !== 'architecture' && matchQ(d))
247
+ .map(d => ({ path: d.rel, title: d.data.title || '', files: (d.data.files || []).join(', ') }));
248
+ }
249
+ const summary = (!isChosen && archDoc) ? (extractSection(archDoc.body, 'Summary') || firstSentence(archDoc.body)) : '';
250
+ return {
251
+ repo, framework: fw, project: e ? e.project : '', role: e ? e.role : '', chosen: isChosen,
252
+ architecture: fileInfo(archRel),
253
+ summary,
254
+ features,
255
+ };
256
+ });
257
+
258
+ // standards per involved framework, filtered by layer
259
+ const wantBackend = layer === 'back-end' || layer === 'backend' || layer === 'hybrid';
260
+ const wantFrontend = layer === 'front-end' || layer === 'frontend' || layer === 'hybrid';
261
+ const standards = [];
262
+ for (const fw of frameworks) {
263
+ if (wantBackend) standards.push({ ...fileInfo(`${fw}/standards/backend-php.md`), framework: fw });
264
+ if (wantFrontend) standards.push({ ...fileInfo(`${fw}/standards/frontend.md`), framework: fw });
265
+ }
266
+
267
+ // client docs (profile + features/workflows filtered to involved frameworks)
268
+ let client = null;
269
+ const slug = args.client && args.client !== 'shared' && args.client !== true ? String(args.client) : null;
270
+ if (slug) {
271
+ const clientDir = path.join(ROOT, 'clients', slug);
272
+ const cDocs = docs.filter(d => d.file.startsWith(clientDir + path.sep));
273
+ const profileDoc = cDocs.find(d => path.basename(d.file) === 'profile.md');
274
+ const cFeatures = cDocs
275
+ .filter(d => path.basename(d.file) !== 'profile.md')
276
+ .filter(d => !frameworks.length || !d.data.framework || frameworks.includes(d.data.framework))
277
+ .filter(matchQ)
278
+ .map(d => ({ path: d.rel, title: d.data.title || '', framework: d.data.framework || '', type: d.data.type || '' }));
279
+ client = {
280
+ slug,
281
+ title: (profileDoc && profileDoc.data.title) || slug,
282
+ profile: fileInfo(`clients/${slug}/profile.md`),
283
+ docs: cFeatures,
284
+ };
285
+ }
286
+
287
+ // ordered read list — mirrors kickoff Step 4 precedence (core first, then chosen
288
+ // repos + features, then standards, then client). Chosen repos → a FULL architecture
289
+ // read; non-chosen → an inline-`summary` entry the skill should NOT open unless it
290
+ // needs deeper detail (lazy:true). Core ordering preserved so foundational context
291
+ // (even when summarized) comes before app context.
292
+ const reads = [];
293
+ const pushRepoRead = (r) => {
294
+ if (r.chosen) {
295
+ reads.push({ label: 'repo-architecture', repo: r.repo, path: r.architecture.path, exists: r.architecture.exists });
296
+ for (const f of r.features) reads.push({ label: 'feature', repo: r.repo, path: f.path, exists: true, title: f.title });
297
+ } else {
298
+ reads.push({ label: 'architecture-summary', repo: r.repo, role: r.role, path: r.architecture.path, exists: r.architecture.exists, lazy: true, summary: r.summary });
299
+ }
300
+ };
301
+ for (const r of repoOut.filter(r => r.role === 'core')) pushRepoRead(r);
302
+ for (const r of repoOut.filter(r => r.role !== 'core')) pushRepoRead(r);
303
+ for (const s of standards) reads.push({ label: 'standard', path: s.path, exists: s.exists });
304
+ if (client) {
305
+ reads.push({ label: 'client-profile', path: client.profile.path, exists: client.profile.exists });
306
+ for (const d of client.docs) reads.push({ label: 'client-doc', path: d.path, exists: true, title: d.title });
307
+ }
308
+
309
+ console.log(JSON.stringify({ frameworks, loadSet, unknown, repos: repoOut, standards, client, reads }));
310
+ }
311
+
173
312
  /* ------------------------------------------------------------------ */
174
313
  /* command: publish — capture's deterministic finish (validate, index, */
175
314
  /* optional mirror, commit knowledge/, rebase-before-push with retry). */
@@ -481,9 +620,10 @@ function main() {
481
620
  case 'deps': return cmdDeps(args);
482
621
  case 'validate': return cmdValidate();
483
622
  case 'manifest': return cmdManifest();
623
+ case 'kickoff-preflight': return cmdPreflight(args);
484
624
  case 'publish': return cmdPublish(args);
485
625
  default:
486
- console.log('Usage: node knowledge.js <search|index|deps|validate|manifest|publish> [--flags]');
626
+ console.log('Usage: node knowledge.js <search|index|deps|validate|manifest|kickoff-preflight|publish> [--flags]');
487
627
  process.exitCode = 1;
488
628
  }
489
629
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.46",
3
+ "version": "1.0.48",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",
@@ -128,33 +128,63 @@ and silently truncates a 7-repo / 5-client list. Render the full list as a numbe
128
128
  list and have the developer reply with numbers (e.g. "1, 3, 5") — no cap, supports
129
129
  multi-select. Framework (q1) and layer (q2) have ≤4 options, so chips are fine there.
130
130
 
131
- ## Step 3 — Resolve the repos this work needs (local paths, lazily)
131
+ ## Step 3 — Preflight: resolve the load-set and the exact read list (one call)
132
132
 
133
- Compute the load set with `node "<TEAM_REPO>/knowledge.js" deps --repo=<chosen-repo>`
134
- (returns the framework core + chosen repo + transitive `dependsOn`, in load order). For
135
- **both** frameworks, run it for each chosen repo and union the results.
133
+ Run **one** command it does the dependency resolution, feature-doc matching, standards
134
+ selection, and client-doc filtering that used to be several `deps`/`search` calls plus
135
+ in-context path-probing:
136
136
 
137
- For **each** repo in the load set, get its local path so you can navigate the actual code:
137
+ ```bash
138
+ node "<TEAM_REPO>/knowledge.js" kickoff-preflight \
139
+ --repos=<comma-separated chosen repos> \
140
+ --client=<slug | omit if shared/internal> \
141
+ --layer=<back-end | front-end | hybrid> \
142
+ --q=<2-4 keywords from the developer's task sentence>
143
+ ```
144
+
145
+ It prints minified JSON:
146
+ - `loadSet` — framework core(s) + chosen repos + transitive `dependsOn`, in load order.
147
+ - `repos[]` — each with `architecture` `{path,exists}`, `role`, `chosen`, an inline
148
+ `summary` (for non-chosen repos), and matched `features[]` (features returned only for the
149
+ repos the developer chose, not deps).
150
+ - `standards[]` and `client` `{title, profile, docs[]}` — both `{path,exists}`-flagged.
151
+ - `reads[]` — **the ordered work list** (see Step 4). Full-read entries carry `{label, path,
152
+ exists}`; non-chosen repos instead carry `{label:'architecture-summary', summary, lazy:true}`
153
+ with the Summary text inlined so no file read is needed.
154
+ - `unknown[]` — any `--repos` value not in the registry (run New-repo onboarding for each).
155
+
156
+ **Do not run `deps` or `search` separately, and do not probe the filesystem for these docs.**
157
+ The preflight already resolved everything; trust `reads[]`.
158
+
159
+ Then, for **each repo in `loadSet`**, get its local code path (so you can navigate the actual
160
+ source — this is the one thing preflight can't know):
138
161
  - Look for a Claude `reference` memory named `repo-path-<repo>` (e.g. `repo-path-worker2`).
139
162
  - If absent, **ask**: "What is the path to the `<repo>` repository?" Validate it exists,
140
163
  then **write a `repo-path-<repo>` memory** (+ MEMORY.md pointer).
141
164
 
142
- Never ask for a repo the session doesn't touch.
165
+ Never ask for a repo not in `loadSet`.
143
166
 
144
167
  ## Step 4 — Load the knowledge
145
168
 
146
- Read and internalize, in this order:
147
- 1. **Framework core architecture** — `<TEAM_REPO>/knowledge/<fw>/apps/<core-repo>/architecture.md`
148
- (e.g. `2.0/apps/_underscore/architecture.md`). Always load this first.
149
- 2. **The chosen repo's** `architecture.md`, then feature docs matched to the description:
150
- `node "<TEAM_REPO>/knowledge.js" search --framework=<fw> --repo=<repo> --q=<keywords>`.
151
- 3. **Coding standards** for the framework(s): `<fw>/standards/backend-php.md` and/or
152
- `frontend.md` per the layer answer (plus any others that exist). Load whichever exist.
153
- 4. **Client knowledge** (if a real client): `clients/<client>/profile.md` and that client's
154
- `features/`/`workflows/` filtered to the selected framework(s)
155
- (`search --client=<client> --framework=<fw>`).
156
-
157
- For **both** frameworks, union across `1.0/` and `2.0/`.
169
+ Walk the preflight `reads[]` array **in order**. Each entry is one of:
170
+
171
+ - **`lazy:true` (label `architecture-summary`)** a repo the developer did NOT choose as
172
+ focus (a dependency or framework core). The entry already carries an inline `summary`
173
+ string **use that summary as your context for this repo; do NOT open the file.** Only
174
+ open its `path` later, on demand, if a task actually requires that repo's deep internals.
175
+ This is the token-saving default: foundational orientation without loading the full doc.
176
+ - **Any other entry (no `lazy` flag)** the chosen repos' architecture + matched feature
177
+ docs, standards, and client docs. **Read these in full**, resolving `path` against
178
+ `<TEAM_REPO>/knowledge/`.
179
+ - **`exists:false`** — skip; that doc isn't written yet. Note it in the Step 5 summary as
180
+ "no knowledge captured yet."
181
+
182
+ The `reads[]` order already encodes the correct precedence (core first, then chosen-repo
183
+ architecture + matched features, then standards, then client profile + client docs) and
184
+ already unions across `1.0/` and `2.0/`. Do not re-derive this order or run further `search`.
185
+
186
+ If, while working, you find you need the full architecture of a summarized (`lazy`) repo,
187
+ just read its `path` then — that's the intended lazy-load, not a kickoff failure.
158
188
 
159
189
  **Do NOT create or modify any `CLAUDE.md` stub** — kickoff only reads. A blank session is a
160
190
  valid choice; this skill is opt-in.