toga-ai 1.0.95 → 1.0.96

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.
@@ -58,6 +58,45 @@ The shared identity/topology of every repo. Maintained **only by the skills**.
58
58
  (`_underscore` for 2.0, `library` for 1.0), plus any transitive `dependsOn`.
59
59
  `node knowledge.js deps --repo=<repo>` resolves the full load order.
60
60
 
61
+ ## Client app-scope (`apps:` on `profile.md`)
62
+
63
+ A single client's work routinely spans **several** repos/frameworks that are not obvious
64
+ from the client's name — e.g. Compass USA touches `_underscore`, `api2`, `toga2-supply`,
65
+ `worker` (1.0), and `dbchanges2`. A developer (or a teammate who only says *"I'm fixing a
66
+ Compass bug"*) should **not** have to know or list those. So each client's `profile.md`
67
+ declares its in-scope repos in an **`apps:`** frontmatter list:
68
+
69
+ ```yaml
70
+ ---
71
+ title: Compass USA
72
+ framework: "2.0"
73
+ apps: # repos auto-loaded whenever this client is mentioned
74
+ - _underscore
75
+ - api2
76
+ - toga2-supply
77
+ - worker
78
+ - dbchanges2
79
+ client: compass-usa
80
+ type: profile
81
+ ...
82
+ ---
83
+ ```
84
+
85
+ - **Auto-load.** `node knowledge.js kickoff-preflight --client=<slug>` unions a client's
86
+ `apps` into the chosen repos, so they are loaded **in full** (architecture + matched
87
+ features) even when the developer names no repos. The command works with `--client` alone.
88
+ The preflight output's `clientScope` block reports which repos came from the client vs. were
89
+ named by the developer. List a `core` repo (e.g. `dbchanges2`, `_underscore`) in `apps` when
90
+ its **full** doc matters for that client's work — otherwise core repos load only as summaries,
91
+ and detail-only rules (e.g. dbchanges2's mandatory `YYYY-MM-DD<letter>` filename suffix) can
92
+ be missed.
93
+ - **Every entry must be a real, registered repo** — `validate` fails on an unknown `apps` repo.
94
+ - **Auto-maintenance (capture's job — see the capture skill).** When a session's work for a
95
+ client touches a repo **not** in that client's `apps`, `capture` **adds it automatically** and
96
+ notes it — a teammate never has to tell us "also load app X for this client." Conversely, a
97
+ developer may explicitly ask to **add** or **remove** a repo from a client's scope, and that
98
+ instruction wins. Removal is the only way a repo leaves `apps`; detection only ever adds.
99
+
61
100
  ## Frontmatter (every doc)
62
101
 
63
102
  ```yaml
@@ -1,6 +1,12 @@
1
1
  ---
2
2
  title: Compass Canada
3
3
  framework: "2.0"
4
+ apps:
5
+ - _underscore
6
+ - api2
7
+ - toga2-supply
8
+ - worker
9
+ - dbchanges2
4
10
  project: _Underscore
5
11
  client: compass-canada
6
12
  type: profile
@@ -1,6 +1,12 @@
1
1
  ---
2
2
  title: Compass USA
3
3
  framework: "2.0"
4
+ apps:
5
+ - _underscore
6
+ - api2
7
+ - toga2-supply
8
+ - worker
9
+ - dbchanges2
4
10
  project: _Underscore
5
11
  client: compass-usa
6
12
  type: profile
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  title: Elite
3
3
  framework: "2.0"
4
+ apps:
5
+ - worker2
6
+ - library
4
7
  project: Worker
5
8
  client: elite
6
9
  type: profile
@@ -1,6 +1,9 @@
1
1
  ---
2
2
  title: New York City Department of Education
3
3
  framework: "1.0"
4
+ apps:
5
+ - worker
6
+ - library
4
7
  project: Worker
5
8
  client: nycdoe
6
9
  type: profile
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  title: Office Depot
3
3
  framework: "2.0"
4
+ apps:
5
+ - voice-to-voice
4
6
  project: TOGa Voice
5
7
  client: office-depot
6
8
  type: profile
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  title: Prudential Financial
3
3
  framework: "2.0"
4
+ apps:
5
+ - _underscore
6
+ - api2
7
+ - dbchanges2
4
8
  project: _Underscore
5
9
  client: prudential
6
10
  type: profile
@@ -1,6 +1,10 @@
1
1
  ---
2
2
  title: "Rate"
3
3
  framework: "2.0"
4
+ apps:
5
+ - _underscore
6
+ - saml
7
+ - toga2-view
4
8
  project: SAML SSO Gateway
5
9
  client: rate
6
10
  type: profile
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  title: "Tow Foundation"
3
3
  framework: "2.0"
4
+ apps:
5
+ - worker2
4
6
  project: Worker
5
7
  client: tow-foundation
6
8
  type: profile
package/knowledge.js CHANGED
@@ -161,13 +161,14 @@ function cmdManifest() {
161
161
  if (fs.existsSync(clientsDir)) {
162
162
  for (const slug of fs.readdirSync(clientsDir, { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name)) {
163
163
  const profile = path.join(clientsDir, slug, 'profile.md');
164
- let title = slug, framework = '';
164
+ let title = slug, framework = '', apps = [];
165
165
  if (fs.existsSync(profile)) {
166
166
  const { data } = parseFrontmatter(fs.readFileSync(profile, 'utf8'));
167
167
  title = data.title || slug;
168
168
  framework = data.framework || '';
169
+ apps = Array.isArray(data.apps) ? data.apps : [];
169
170
  }
170
- clients.push({ slug, title, framework });
171
+ clients.push({ slug, title, framework, apps });
171
172
  }
172
173
  }
173
174
  console.log(JSON.stringify({ repos, clients }));
@@ -198,9 +199,23 @@ function extractSection(body, heading) {
198
199
 
199
200
  function cmdPreflight(args) {
200
201
  const registry = loadRegistry();
201
- const chosen = String(args.repos || '').split(',').map(s => s.trim()).filter(Boolean);
202
+ const devChosen = String(args.repos || '').split(',').map(s => s.trim()).filter(Boolean);
203
+ // Client-scoped apps: when a client is named, auto-include the repos listed in its
204
+ // profile.md `apps:` so a teammate who only mentions the client still gets every
205
+ // app/framework that client's work spans — without having to name them. Unknown
206
+ // repo names are ignored (validate flags them separately).
207
+ const slug = args.client && args.client !== 'shared' && args.client !== true ? String(args.client) : null;
208
+ let clientApps = [];
209
+ if (slug) {
210
+ const profilePath = path.join(ROOT, 'clients', slug, 'profile.md');
211
+ if (fs.existsSync(profilePath)) {
212
+ const { data } = parseFrontmatter(fs.readFileSync(profilePath, 'utf8'));
213
+ clientApps = (Array.isArray(data.apps) ? data.apps : []).filter(r => registry.find(x => x.repo === r));
214
+ }
215
+ }
216
+ const chosen = [...new Set([...devChosen, ...clientApps])];
202
217
  if (!chosen.length) {
203
- console.log(JSON.stringify({ error: 'PREFLIGHT: --repos=<repo,repo> required' }));
218
+ console.log(JSON.stringify({ error: 'PREFLIGHT: provide --repos=<repo,repo> and/or --client=<slug> whose profile lists apps' }));
204
219
  process.exitCode = 1;
205
220
  return;
206
221
  }
@@ -266,7 +281,6 @@ function cmdPreflight(args) {
266
281
 
267
282
  // client docs (profile + features/workflows filtered to involved frameworks)
268
283
  let client = null;
269
- const slug = args.client && args.client !== 'shared' && args.client !== true ? String(args.client) : null;
270
284
  if (slug) {
271
285
  const clientDir = path.join(ROOT, 'clients', slug);
272
286
  const cDocs = docs.filter(d => d.file.startsWith(clientDir + path.sep));
@@ -306,7 +320,11 @@ function cmdPreflight(args) {
306
320
  for (const d of client.docs) reads.push({ label: 'client-doc', path: d.path, exists: true, title: d.title });
307
321
  }
308
322
 
309
- console.log(JSON.stringify({ frameworks, loadSet, unknown, repos: repoOut, standards, client, reads }));
323
+ // clientScope tells the kickoff skill which repos were auto-pulled from the client's
324
+ // profile.md `apps:` (vs. named by the developer) so it can show "loaded because you
325
+ // mentioned <client>" and the dev can still add/remove.
326
+ const clientScope = { client: slug, fromClient: clientApps, fromDev: devChosen };
327
+ console.log(JSON.stringify({ frameworks, loadSet, unknown, clientScope, repos: repoOut, standards, client, reads }));
310
328
  }
311
329
 
312
330
  /* ------------------------------------------------------------------ */
@@ -635,6 +653,13 @@ function cmdValidate() {
635
653
  if (entry && d.data.project !== entry.project) { fail(`${d.rel}: project "${d.data.project}" != registry "${entry.project}"`); ok = false; }
636
654
  }
637
655
  }
656
+ // client profile `apps:` must reference real, registered repos (the client-scope
657
+ // load-set the kickoff skill auto-loads when this client is mentioned).
658
+ if (d.data.type === 'profile' && Array.isArray(d.data.apps)) {
659
+ for (const app of d.data.apps) {
660
+ if (!registry.find(r => r.repo === app)) { fail(`${d.rel}: apps lists unknown repo "${app}"`); ok = false; }
661
+ }
662
+ }
638
663
  if (ELEVATED_TYPES.includes(d.data.type)) elevated.push(d.rel);
639
664
  for (const s of scanSecrets(fs.readFileSync(d.file, 'utf8'))) {
640
665
  fail(`${d.rel}:${s.line}: possible secret committed — ${s.reason}; redact it (reference the config/constant location, never paste credential values into knowledge docs)`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.95",
3
+ "version": "1.0.96",
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",
@@ -189,6 +189,30 @@ config key, the file) but not its value. `validate` (run by Step 6's publish and
189
189
  pre-commit hook) scans for credential literals and **fails the publish** if it finds one, so
190
190
  a leaked secret blocks the whole capture until redacted. Reference, never reproduce.
191
191
 
192
+ ## Step 5b — Maintain the client's app-scope (`apps:` on `profile.md`)
193
+
194
+ When this session's work was **for a specific client**, keep that client's `apps` list (the
195
+ repos auto-loaded whenever the client is mentioned — see `CONVENTIONS.md` → *Client app-scope*)
196
+ in sync. **This is automatic — a teammate must never have to tell us "also load app X for this
197
+ client."**
198
+
199
+ 1. **Detect.** Collect every repo this session actually touched (from the files changed and the
200
+ `files:` you wrote on docs this capture). Read the client's `clients/<slug>/profile.md`
201
+ `apps:` list.
202
+ 2. **Auto-add.** For any touched repo **not** already in `apps`, add it to the profile's `apps`
203
+ list (and bump `updated`). Detection only ever **adds** — never silently removes. Briefly note
204
+ each addition in the Step 7 report (e.g. "Added `dbchanges2` to Compass USA scope"). Add a
205
+ `core` repo (e.g. `dbchanges2`, `_underscore`) when its **full** doc — not just its summary —
206
+ mattered to the work, so the next session loads it in full.
207
+ 3. **Honor explicit instructions (these win).** If the developer said to **add** or **remove** a
208
+ repo from the client's scope this session, apply it: add even if untouched, or remove even if
209
+ touched. Removal happens **only** on an explicit request.
210
+ 4. **New client with no `apps` yet?** Seed it from the repos this session touched.
211
+
212
+ The `apps` change is an ordinary `profile.md` edit (not elevated) — apply it without asking,
213
+ then it publishes with everything else in Step 6. `validate` rejects an `apps` entry that is not
214
+ a registered repo, so only add real repos (run New-repo onboarding first if needed).
215
+
192
216
  ## Step 6 — Publish (validate → index → mirror → rebase-push, one command)
193
217
 
194
218
  Finish every capture with the deterministic publisher — **do not** run validate/index/git
@@ -366,6 +390,8 @@ any client docs:
366
390
  ---
367
391
  title: "<Formal Client Name>" # the client's formal name IS the title; slug is only the folder
368
392
  framework: "<1.0|2.0>"
393
+ apps: # repos auto-loaded when this client is mentioned (seed from
394
+ - <repo> # the repos this session touched; maintained per Step 5b)
369
395
  project: <Project>
370
396
  client: <slug>
371
397
  type: profile
@@ -138,7 +138,7 @@ When resolved, if there was no `team-repo-path` memory, **write one** (memory ty
138
138
  ## Step 2 — Interview the developer (ask the work questions FIRST)
139
139
 
140
140
  Get the pickable lists in **one call**: `node "<TEAM_REPO>/knowledge.js" manifest` returns
141
- minified JSON `{repos:[{repo,project,framework,role}], clients:[{slug,title,framework}]}`.
141
+ minified JSON `{repos:[{repo,project,framework,role}], clients:[{slug,title,framework,apps}]}`.
142
142
  **Do not read `registry.json` or any `profile.md` to build these lists.** Ask all questions
143
143
  in one message:
144
144
 
@@ -147,12 +147,25 @@ in one message:
147
147
  3. **Repo(s) / project(s)** — list EVERY manifest repo for the chosen framework(s).
148
148
  **Multi-select (checkbox), not single choice** — a session often spans several repos. If
149
149
  they name a repo not listed, run **New-repo onboarding** (below) for each before continuing.
150
+ **This question is OPTIONAL when a client is chosen** — that client's `apps` are auto-loaded
151
+ (see the client-scope note below), so a developer who only knows the client need not pick
152
+ repos. Use it to let them *add* repos beyond the client's scope.
150
153
  4. **Client** — list EVERY manifest client by its **`title`** (the formal name, not the slug),
151
154
  plus "shared / internal" and "a new client".
152
155
  5. **What are you building or changing?** (a sentence — used to pick relevant feature docs.)
153
156
 
154
157
  If any answer is unclear, ask again. Do not guess framework, repo, project, or client.
155
158
 
159
+ **Client app-scope (do this automatically — never make the developer enumerate apps).** Each
160
+ client in the manifest carries an `apps` list — the repos/frameworks that client's work spans
161
+ (e.g. Compass USA → `_underscore, api2, toga2-supply, worker, dbchanges2`). When a client is
162
+ chosen, **all of its `apps` are loaded for you by preflight** even if the developer named no
163
+ repos — so "I'm fixing a Compass bug" is enough. After loading, briefly tell the developer
164
+ which repos were pulled in because of the client (from preflight's `clientScope.fromClient`),
165
+ and that they can say **"also add `<repo>`"** or **"drop `<repo>` for this session"** to adjust.
166
+ A session-only add/remove does **not** change the client's stored scope; a request to change it
167
+ **for this client going forward** is a profile edit captured at `/capture` time.
168
+
156
169
  **Presenting q3 and q4:** do NOT use the option-chip / radio control — it caps at ~4 options
157
170
  and silently truncates a 7-repo / 5-client list. Render the full list as a numbered markdown
158
171
  list and have the developer reply with numbers (e.g. "1, 3, 5") — no cap, supports
@@ -172,8 +185,15 @@ node "<TEAM_REPO>/knowledge.js" kickoff-preflight \
172
185
  --q=<2-4 keywords from the developer's task sentence>
173
186
  ```
174
187
 
188
+ `--repos` is **optional when `--client` is given** — the client's `apps` are auto-unioned into
189
+ the load-set, so `--client=<slug>` alone resolves the full set. Pass `--repos` only to add repos
190
+ the developer named beyond the client's scope (or for a shared/internal session with no client).
191
+
175
192
  It prints minified JSON:
176
- - `loadSet` — framework core(s) + chosen repos + transitive `dependsOn`, in load order.
193
+ - `loadSet` — framework core(s) + chosen repos + the client's `apps` + transitive `dependsOn`,
194
+ in load order.
195
+ - `clientScope` — `{client, fromClient[], fromDev[]}`: which repos were auto-loaded from the
196
+ client's profile vs. named by the developer. Surface `fromClient` to the developer in Step 5.
177
197
  - `repos[]` — each with `architecture` `{path,exists}`, `role`, `chosen`, an inline
178
198
  `summary` (for non-chosen repos), and matched `features[]` (features returned only for the
179
199
  repos the developer chose, not deps).