toga-ai 1.0.46 → 1.0.47
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 +118 -3
- package/package.json +1 -1
- package/skills/kickoff/SKILL.md +34 -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,117 @@ 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 cmdPreflight(args) {
|
|
187
|
+
const registry = loadRegistry();
|
|
188
|
+
const chosen = String(args.repos || '').split(',').map(s => s.trim()).filter(Boolean);
|
|
189
|
+
if (!chosen.length) {
|
|
190
|
+
console.log(JSON.stringify({ error: 'PREFLIGHT: --repos=<repo,repo> required' }));
|
|
191
|
+
process.exitCode = 1;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const layer = String(args.layer || 'hybrid').toLowerCase();
|
|
195
|
+
const q = args.q ? String(args.q).toLowerCase() : null;
|
|
196
|
+
const unknown = chosen.filter(r => !registry.find(x => x.repo === r));
|
|
197
|
+
|
|
198
|
+
// frameworks involved (from the chosen repos)
|
|
199
|
+
const fwSet = new Set();
|
|
200
|
+
for (const r of chosen) { const e = registry.find(x => x.repo === r); if (e) fwSet.add(e.framework); }
|
|
201
|
+
const frameworks = [...fwSet];
|
|
202
|
+
|
|
203
|
+
// dependency load-set: framework core(s) first, then transitive dependsOn, then chosen
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
const loadSet = [];
|
|
206
|
+
const visit = (r) => {
|
|
207
|
+
const e = registry.find(x => x.repo === r);
|
|
208
|
+
if (!e) return;
|
|
209
|
+
for (const d of (e.dependsOn || [])) visit(d);
|
|
210
|
+
if (!seen.has(r)) { seen.add(r); loadSet.push(r); }
|
|
211
|
+
};
|
|
212
|
+
for (const fw of frameworks) for (const core of coreReposFor(registry, fw)) visit(core);
|
|
213
|
+
for (const r of chosen) visit(r);
|
|
214
|
+
|
|
215
|
+
const docs = allDocs();
|
|
216
|
+
const chosenSet = new Set(chosen);
|
|
217
|
+
const fileInfo = (rel) => ({ path: rel, exists: fs.existsSync(path.join(ROOT, rel.split('/').join(path.sep))) });
|
|
218
|
+
const matchQ = (d) => !q || (((d.data.title || '') + ' ' + (d.data.files || []).join(' ') + ' ' + d.body).toLowerCase().includes(q));
|
|
219
|
+
|
|
220
|
+
// per-repo inventory; feature docs only for explicitly chosen repos
|
|
221
|
+
const repoOut = loadSet.map(repo => {
|
|
222
|
+
const e = registry.find(x => x.repo === repo);
|
|
223
|
+
const fw = e ? e.framework : '';
|
|
224
|
+
const repoDir = path.join(ROOT, fw, 'apps', repo);
|
|
225
|
+
const isChosen = chosenSet.has(repo);
|
|
226
|
+
let features = [];
|
|
227
|
+
if (isChosen) {
|
|
228
|
+
features = docs
|
|
229
|
+
.filter(d => d.file.startsWith(repoDir + path.sep) && d.data.type !== 'architecture' && matchQ(d))
|
|
230
|
+
.map(d => ({ path: d.rel, title: d.data.title || '', files: (d.data.files || []).join(', ') }));
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
repo, framework: fw, project: e ? e.project : '', role: e ? e.role : '', chosen: isChosen,
|
|
234
|
+
architecture: fileInfo(`${fw}/apps/${repo}/architecture.md`),
|
|
235
|
+
features,
|
|
236
|
+
};
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// standards per involved framework, filtered by layer
|
|
240
|
+
const wantBackend = layer === 'back-end' || layer === 'backend' || layer === 'hybrid';
|
|
241
|
+
const wantFrontend = layer === 'front-end' || layer === 'frontend' || layer === 'hybrid';
|
|
242
|
+
const standards = [];
|
|
243
|
+
for (const fw of frameworks) {
|
|
244
|
+
if (wantBackend) standards.push({ ...fileInfo(`${fw}/standards/backend-php.md`), framework: fw });
|
|
245
|
+
if (wantFrontend) standards.push({ ...fileInfo(`${fw}/standards/frontend.md`), framework: fw });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// client docs (profile + features/workflows filtered to involved frameworks)
|
|
249
|
+
let client = null;
|
|
250
|
+
const slug = args.client && args.client !== 'shared' && args.client !== true ? String(args.client) : null;
|
|
251
|
+
if (slug) {
|
|
252
|
+
const clientDir = path.join(ROOT, 'clients', slug);
|
|
253
|
+
const cDocs = docs.filter(d => d.file.startsWith(clientDir + path.sep));
|
|
254
|
+
const profileDoc = cDocs.find(d => path.basename(d.file) === 'profile.md');
|
|
255
|
+
const cFeatures = cDocs
|
|
256
|
+
.filter(d => path.basename(d.file) !== 'profile.md')
|
|
257
|
+
.filter(d => !frameworks.length || !d.data.framework || frameworks.includes(d.data.framework))
|
|
258
|
+
.filter(matchQ)
|
|
259
|
+
.map(d => ({ path: d.rel, title: d.data.title || '', framework: d.data.framework || '', type: d.data.type || '' }));
|
|
260
|
+
client = {
|
|
261
|
+
slug,
|
|
262
|
+
title: (profileDoc && profileDoc.data.title) || slug,
|
|
263
|
+
profile: fileInfo(`clients/${slug}/profile.md`),
|
|
264
|
+
docs: cFeatures,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ordered read list — mirrors kickoff Step 4: core arch → repo arch+features → standards → client
|
|
269
|
+
const reads = [];
|
|
270
|
+
for (const r of repoOut.filter(r => r.role === 'core')) {
|
|
271
|
+
reads.push({ label: 'core-architecture', repo: r.repo, ...r.architecture });
|
|
272
|
+
for (const f of r.features) reads.push({ label: 'feature', repo: r.repo, path: f.path, exists: true, title: f.title });
|
|
273
|
+
}
|
|
274
|
+
for (const r of repoOut.filter(r => r.role !== 'core')) {
|
|
275
|
+
reads.push({ label: r.chosen ? 'repo-architecture' : 'dep-architecture', repo: r.repo, ...r.architecture });
|
|
276
|
+
for (const f of r.features) reads.push({ label: 'feature', repo: r.repo, path: f.path, exists: true, title: f.title });
|
|
277
|
+
}
|
|
278
|
+
for (const s of standards) reads.push({ label: 'standard', path: s.path, exists: s.exists });
|
|
279
|
+
if (client) {
|
|
280
|
+
reads.push({ label: 'client-profile', path: client.profile.path, exists: client.profile.exists });
|
|
281
|
+
for (const d of client.docs) reads.push({ label: 'client-doc', path: d.path, exists: true, title: d.title });
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
console.log(JSON.stringify({ frameworks, loadSet, unknown, repos: repoOut, standards, client, reads }));
|
|
285
|
+
}
|
|
286
|
+
|
|
173
287
|
/* ------------------------------------------------------------------ */
|
|
174
288
|
/* command: publish — capture's deterministic finish (validate, index, */
|
|
175
289
|
/* optional mirror, commit knowledge/, rebase-before-push with retry). */
|
|
@@ -481,9 +595,10 @@ function main() {
|
|
|
481
595
|
case 'deps': return cmdDeps(args);
|
|
482
596
|
case 'validate': return cmdValidate();
|
|
483
597
|
case 'manifest': return cmdManifest();
|
|
598
|
+
case 'kickoff-preflight': return cmdPreflight(args);
|
|
484
599
|
case 'publish': return cmdPublish(args);
|
|
485
600
|
default:
|
|
486
|
-
console.log('Usage: node knowledge.js <search|index|deps|validate|manifest|publish> [--flags]');
|
|
601
|
+
console.log('Usage: node knowledge.js <search|index|deps|validate|manifest|kickoff-preflight|publish> [--flags]');
|
|
487
602
|
process.exitCode = 1;
|
|
488
603
|
}
|
|
489
604
|
}
|
package/package.json
CHANGED
package/skills/kickoff/SKILL.md
CHANGED
|
@@ -128,33 +128,49 @@ 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`, and matched
|
|
148
|
+
`features[]` (features are returned only for the repos the developer chose, not deps).
|
|
149
|
+
- `standards[]` and `client` `{title, profile, docs[]}` — both `{path,exists}`-flagged.
|
|
150
|
+
- `reads[]` — **the ordered list of knowledge files to read**, each `{label, path, exists}`.
|
|
151
|
+
- `unknown[]` — any `--repos` value not in the registry (run New-repo onboarding for each).
|
|
152
|
+
|
|
153
|
+
**Do not run `deps` or `search` separately, and do not probe the filesystem for these docs.**
|
|
154
|
+
The preflight already resolved everything; trust `reads[]`.
|
|
155
|
+
|
|
156
|
+
Then, for **each repo in `loadSet`**, get its local code path (so you can navigate the actual
|
|
157
|
+
source — this is the one thing preflight can't know):
|
|
138
158
|
- Look for a Claude `reference` memory named `repo-path-<repo>` (e.g. `repo-path-worker2`).
|
|
139
159
|
- If absent, **ask**: "What is the path to the `<repo>` repository?" Validate it exists,
|
|
140
160
|
then **write a `repo-path-<repo>` memory** (+ MEMORY.md pointer).
|
|
141
161
|
|
|
142
|
-
Never ask for a repo
|
|
162
|
+
Never ask for a repo not in `loadSet`.
|
|
143
163
|
|
|
144
164
|
## Step 4 — Load the knowledge
|
|
145
165
|
|
|
146
|
-
Read
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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/`.
|
|
166
|
+
Read every entry in the preflight `reads[]` array **in order**, resolving each `path`
|
|
167
|
+
against `<TEAM_REPO>/knowledge/`. Skip entries with `exists:false` — that doc isn't written
|
|
168
|
+
yet; note it in the Step 5 summary as "no knowledge captured yet."
|
|
169
|
+
|
|
170
|
+
The `reads[]` order already encodes the correct precedence (framework core architecture →
|
|
171
|
+
chosen-repo architecture + matched features → dep architecture → standards → client profile +
|
|
172
|
+
client docs), and already unions across `1.0/` and `2.0/` for a both-frameworks session. You
|
|
173
|
+
do not need to re-derive this order or run any further `search`.
|
|
158
174
|
|
|
159
175
|
**Do NOT create or modify any `CLAUDE.md` stub** — kickoff only reads. A blank session is a
|
|
160
176
|
valid choice; this skill is opt-in.
|