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 +143 -3
- package/package.json +1 -1
- package/skills/kickoff/SKILL.md +48 -18
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
|
|
17
|
+
* search --framework= --repo= --project= --client= --file= --q=
|
|
18
18
|
* index
|
|
19
|
-
* deps
|
|
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
package/skills/kickoff/SKILL.md
CHANGED
|
@@ -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 —
|
|
131
|
+
## Step 3 — Preflight: resolve the load-set and the exact read list (one call)
|
|
132
132
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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
|
|
165
|
+
Never ask for a repo not in `loadSet`.
|
|
143
166
|
|
|
144
167
|
## Step 4 — Load the knowledge
|
|
145
168
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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.
|