toga-ai 1.0.1021 → 1.0.1023
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/.claude/settings.json +9 -1
- package/CLAUDE.md +3 -7
- package/README.md +9 -9
- package/agents/harness-optimizer.md +1 -1
- package/agents/session-capture.md +1 -1
- package/knowledge/1.0/apps/worker/features/staples-asn-856-outbound.md +11 -2
- package/knowledge.js +4 -4
- package/package.json +2 -4
- package/rules/common/communication.md +1 -1
- package/scripts/harness.js +0 -26
- package/scripts/hooks/harness-version-check.js +74 -0
- package/scripts/hooks/lib/npm-latest.js +45 -0
- package/scripts/install.js +10 -24
- package/skills/finish/SKILL.md +3 -3
- package/skills/migrate-github-org/SKILL.md +1 -1
- package/skills/session-save/SKILL.md +1 -1
- package/skills/start/SKILL.md +2 -0
- package/skills/team-skills/SKILL.md +7 -7
- package/sync-skills.js +0 -71
package/.claude/settings.json
CHANGED
|
@@ -234,6 +234,15 @@
|
|
|
234
234
|
"timeout": 5000
|
|
235
235
|
}
|
|
236
236
|
]
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"hooks": [
|
|
240
|
+
{
|
|
241
|
+
"type": "command",
|
|
242
|
+
"command": "node \"$CLAUDE_PROJECT_DIR/.claude/hooks/toga/harness-version-check.js\"",
|
|
243
|
+
"timeout": 5000
|
|
244
|
+
}
|
|
245
|
+
]
|
|
237
246
|
}
|
|
238
247
|
],
|
|
239
248
|
"Stop": [
|
|
@@ -270,7 +279,6 @@
|
|
|
270
279
|
"allow": [
|
|
271
280
|
"Bash(node .claude/knowledge.js *)",
|
|
272
281
|
"Bash(node .claude/hooks/toga/*.js)",
|
|
273
|
-
"Bash(node sync-skills.js *)",
|
|
274
282
|
"Bash(node scripts/harness.js *)",
|
|
275
283
|
"Bash(node scripts/install.js *)",
|
|
276
284
|
"Bash(php -l *)",
|
package/CLAUDE.md
CHANGED
|
@@ -13,7 +13,7 @@ loops that keep the main conversation lean.
|
|
|
13
13
|
|
|
14
14
|
## Skills (Claude Code slash commands)
|
|
15
15
|
|
|
16
|
-
Install them into any project with `
|
|
16
|
+
Install them into any project with `npx toga-ai@latest`, then call them
|
|
17
17
|
as `/skill-name` inside Claude Code.
|
|
18
18
|
|
|
19
19
|
| Skill | Command | When to use |
|
|
@@ -63,7 +63,7 @@ contents.
|
|
|
63
63
|
|
|
64
64
|
### Agents
|
|
65
65
|
|
|
66
|
-
These install into a project's `.claude/agents/toga/` via `npx toga-ai`:
|
|
66
|
+
These install into a project's `.claude/agents/toga/` via `npx toga-ai@latest`:
|
|
67
67
|
|
|
68
68
|
- **context-primer** — DEPRECATED (2026-08-13); `/start` now primes in the main session, not via this agent.
|
|
69
69
|
- **session-capture** — capture write pipeline; search → classify → draft → write → publish.
|
|
@@ -207,14 +207,10 @@ node knowledge.js search --framework=2.0 --repo=worker2 --q=<keywords>
|
|
|
207
207
|
# Resolve dependency load order for a repo
|
|
208
208
|
node knowledge.js deps --repo=worker2
|
|
209
209
|
|
|
210
|
-
# Sync skills into a project
|
|
211
|
-
node sync-skills.js [project-path]
|
|
212
|
-
|
|
213
210
|
# Harness utilities (audit, status, new-repo wizard)
|
|
214
211
|
node scripts/harness.js audit
|
|
215
212
|
node scripts/harness.js status
|
|
216
213
|
node scripts/harness.js new-repo
|
|
217
|
-
node scripts/harness.js sync [project-path]
|
|
218
214
|
```
|
|
219
215
|
|
|
220
216
|
---
|
|
@@ -308,5 +304,5 @@ hook runs automatically and warns you of any frontmatter or schema errors.
|
|
|
308
304
|
|
|
309
305
|
Pull latest skills, agents, and knowledge from the team repo:
|
|
310
306
|
```
|
|
311
|
-
npx toga-ai
|
|
307
|
+
npx toga-ai@latest
|
|
312
308
|
```
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Shared Claude Code skills, configuration, and the team **knowledge base** for TO
|
|
|
6
6
|
|
|
7
7
|
Do this once per developer. The harness has two independent parts: the **ECC plugin**
|
|
8
8
|
(provides the `ecc:*` reviewer agents the TOGA agents delegate to) and the **TOGA harness**
|
|
9
|
-
(`npx toga-ai`, which installs the skills, agents, rules, and knowledge base). Install order
|
|
9
|
+
(`npx toga-ai@latest`, which installs the skills, agents, rules, and knowledge base). Install order
|
|
10
10
|
between the two does not matter.
|
|
11
11
|
|
|
12
12
|
**Prerequisites** (must already be installed):
|
|
@@ -28,7 +28,7 @@ between the two does not matter.
|
|
|
28
28
|
```
|
|
29
29
|
3. Open a **new terminal**, and **from inside the project repo you'll work in**, run:
|
|
30
30
|
```
|
|
31
|
-
npx toga-ai
|
|
31
|
+
npx toga-ai@latest
|
|
32
32
|
```
|
|
33
33
|
This installs the harness into that project's `.claude/` and clones the shared knowledge
|
|
34
34
|
repo to `~/toga-tech`. **Run it once in each project folder** you work in.
|
|
@@ -39,7 +39,7 @@ between the two does not matter.
|
|
|
39
39
|
> `agilantsolutions/claude`. Each developer must be a **collaborator on that repo with git
|
|
40
40
|
> credentials configured** (HTTPS token / credential manager or SSH). Without it, `/finish`
|
|
41
41
|
> still writes docs locally but the push fails silently — so the developer won't be
|
|
42
|
-
> contributing knowledge back. `npx toga-ai` then delivers everyone's captured knowledge to
|
|
42
|
+
> contributing knowledge back. `npx toga-ai@latest` then delivers everyone's captured knowledge to
|
|
43
43
|
> the whole team on each run.
|
|
44
44
|
|
|
45
45
|
## Skills
|
|
@@ -92,21 +92,21 @@ developer references a repo on a new machine, the skill asks "What is the path t
|
|
|
92
92
|
## How skills are deployed
|
|
93
93
|
|
|
94
94
|
Claude Code loads skills from `.claude/skills/` at the project root. The skills in this repo
|
|
95
|
-
are the source of truth —
|
|
95
|
+
are the source of truth — `npx toga-ai@latest` copies them into that directory.
|
|
96
96
|
|
|
97
|
-
##
|
|
97
|
+
## Updating skills
|
|
98
98
|
|
|
99
|
-
|
|
99
|
+
Run this from the project root to install the latest skills, agents, rules, and knowledge:
|
|
100
100
|
|
|
101
101
|
```
|
|
102
|
-
|
|
102
|
+
npx toga-ai@latest
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
Works on Mac and PC.
|
|
105
|
+
Works on Mac and PC.
|
|
106
106
|
|
|
107
107
|
## Adding a skill
|
|
108
108
|
|
|
109
109
|
1. Create a directory under `skills/` with your skill name.
|
|
110
110
|
2. Add a `SKILL.md` with YAML frontmatter (`name`, `description`) and the instructions.
|
|
111
|
-
3.
|
|
111
|
+
3. Publish a new `toga-ai` version; teammates get it on `npx toga-ai@latest`.
|
|
112
112
|
4. Update the table above.
|
|
@@ -66,7 +66,7 @@ Score: <N>/100
|
|
|
66
66
|
**Overall: 72/100**
|
|
67
67
|
|
|
68
68
|
### Priority Fixes (by score impact)
|
|
69
|
-
1. [HOOKS -15pts] post-edit-validate.js exits with error → `npx toga-ai` to reinstall
|
|
69
|
+
1. [HOOKS -15pts] post-edit-validate.js exits with error → `npx toga-ai@latest` to reinstall
|
|
70
70
|
2. [KNOWLEDGE -10pts] validate ERROR in worker2/architecture.md → fix frontmatter
|
|
71
71
|
3. [ECC +5pts] Install ECC for 200+ specialist agents → `claude plugin add ecc`
|
|
72
72
|
```
|
|
@@ -119,7 +119,7 @@ so the whole capture pushes once after approval. Act on the `PUBLISH:` line:
|
|
|
119
119
|
`PUSHED` → done · `NO_CHANGES` → nothing durable · `VALIDATE_FAILED` → fix the printed
|
|
120
120
|
`ERROR:` lines and re-publish · `CONFLICT` → resolve, re-publish ·
|
|
121
121
|
`ABORT_OUTSIDE_KNOWLEDGE` → investigate · `NOT_GIT` → local-only, tell dev to run
|
|
122
|
-
`npx toga-ai` · `PUSH_FAILED` → return the manual-push command.
|
|
122
|
+
`npx toga-ai@latest` · `PUSH_FAILED` → return the manual-push command.
|
|
123
123
|
|
|
124
124
|
## Step 7 — Report (your entire output, kept short)
|
|
125
125
|
|
|
@@ -6,7 +6,7 @@ project: Worker
|
|
|
6
6
|
client: staples
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-10-
|
|
9
|
+
updated: 2026-10-14
|
|
10
10
|
owners: [mhammontree]
|
|
11
11
|
files:
|
|
12
12
|
- worker/crons/sync/staples/staples_asn_netsuite.php
|
|
@@ -20,7 +20,7 @@ related:
|
|
|
20
20
|
- ../../library/features/mvc-page-pattern-and-app-skeleton.md
|
|
21
21
|
---
|
|
22
22
|
|
|
23
|
-
Hourly cron sending Staples 856 ASNs for shipped NetSuite item fulfillments, one row per fulfillment in `Common.StaplesAdvanceShipNotices`; open for the queue/retry/dedupe design, the 855 gate, statuses, manual SKIPPED, alerts,
|
|
23
|
+
Hourly cron sending Staples 856 ASNs for shipped NetSuite item fulfillments, one row per fulfillment in `Common.StaplesAdvanceShipNotices`; open for the queue/retry/dedupe design, the 855 gate, statuses, manual SKIPPED, alerts, how to test it locally, and how to check a "missing ASN" report.
|
|
24
24
|
|
|
25
25
|
## Summary
|
|
26
26
|
|
|
@@ -48,6 +48,14 @@ Each run (`cronInitialization(14400, false)` — must **not** use the read clust
|
|
|
48
48
|
|
|
49
49
|
**Deploy order:** merge dbchanges first (`_production` applies on the next worker deploy), then deploy worker.
|
|
50
50
|
|
|
51
|
+
## Checking a missing-ASN report
|
|
52
|
+
|
|
53
|
+
1. **Table** (env `legacy`, 1.0 prod): `SELECT * FROM Common.StaplesAdvanceShipNotices WHERE poNumber IN (...)`. `SENT` + `dtSent` + `fileName` is the main proof. `dt*` columns are Central time (America/Chicago). The last part of `856_<PO>_<fulfillmentInternalId>_<n>` is the send's Unix timestamp — compare it to the SFTP file time.
|
|
54
|
+
2. **NetSuite** `itemship.nl?id=<itemFulfillmentInternalId>`: Date (created), Shipped Date, SO. The job queues a fulfillment only once `shipStatus` is `_shipped`, so the row's `dtCreated` is the first run that saw it Shipped.
|
|
55
|
+
3. **SFTP `/archive`** holds `855_<PO>_*` and `856_<PO>_*` once Staples picked them up.
|
|
56
|
+
|
|
57
|
+
Usual answer (TRUE-82655, 9 POs over 2 rounds): every ASN was sent; the client checked before the fulfillment was Shipped in NetSuite (shipping was 1-3 days after the 855). No code issue. Client proof reports were built from these 3 sources.
|
|
58
|
+
|
|
51
59
|
## Testing locally
|
|
52
60
|
|
|
53
61
|
1. Load prod `Common` locally and run `dbchanges/Common/MH/2026-09-30.sql`.
|
|
@@ -65,3 +73,4 @@ Expected (2026-10-01 run): first run 101 rows, 90 matched as already sent by the
|
|
|
65
73
|
|
|
66
74
|
## Change history
|
|
67
75
|
- 2026-10-01 — First doc. TRUE-82180 rewrite: `StaplesAdvanceShipNotices` queue with 7-day lookback, shipping-hash dedupe, per-row retry/FAILED, 24 h alert, test mode never uploads, old-job match for pre-go-live shipments. Committed worker `cf5198a27`, dbchanges `8b1f7cb` on branch TRUE-82180; not deployed as of 2026-10-01. (mhammontree)
|
|
76
|
+
- 2026-10-14 — Added "Checking a missing-ASN report" (TRUE-82655 investigation: all reported ASNs were sent, client checked before Shipped). (mhammontree)
|
package/knowledge.js
CHANGED
|
@@ -459,7 +459,7 @@ function cmdPublish(args) {
|
|
|
459
459
|
|
|
460
460
|
// 0. must be a git clone
|
|
461
461
|
try { run('git rev-parse --git-dir'); }
|
|
462
|
-
catch { console.log('PUBLISH: NOT_GIT — knowledge written locally only; run `npx toga-ai` to create a pushable clone'); return; }
|
|
462
|
+
catch { console.log('PUBLISH: NOT_GIT — knowledge written locally only; run `npx toga-ai@latest` to create a pushable clone'); return; }
|
|
463
463
|
|
|
464
464
|
// 1. validate (inherit output so any ERROR: lines are visible); abort on failure
|
|
465
465
|
try { cp.execSync(`"${node}" "${self}" validate`, { cwd: TEAM, stdio: 'inherit' }); }
|
|
@@ -498,7 +498,7 @@ function cmdPublish(args) {
|
|
|
498
498
|
run('git fetch origin');
|
|
499
499
|
run(`git rebase --autostash origin/${branch}`);
|
|
500
500
|
run(`git push origin HEAD:${branch}`);
|
|
501
|
-
console.log(`PUBLISH: PUSHED to ${branch} — CI publishes a new npm version; teammates get it on \`npx toga-ai\``);
|
|
501
|
+
console.log(`PUBLISH: PUSHED to ${branch} — CI publishes a new npm version; teammates get it on \`npx toga-ai@latest\``);
|
|
502
502
|
return;
|
|
503
503
|
} catch (e) {
|
|
504
504
|
lastError = String((e && (e.stderr || e.stdout)) || (e && e.message) || '').trim();
|
|
@@ -944,7 +944,7 @@ function cmdSessionSave(args) {
|
|
|
944
944
|
if (!['active', 'stale'].includes(data.status)) { console.log('SESSION: INVALID — status must be active|stale'); process.exitCode = 1; return; }
|
|
945
945
|
const secrets = scanSecrets(content);
|
|
946
946
|
if (secrets.length) { console.log('SESSION: SECRET — ' + secrets.map(s => `line ${s.line}: ${s.reason}`).join('; ') + '; redact before saving'); process.exitCode = 1; return; }
|
|
947
|
-
if (!isGitClone()) { console.log('SESSION: NOT_GIT — session written locally only; run `npx toga-ai` to create a pushable clone'); return; }
|
|
947
|
+
if (!isGitClone()) { console.log('SESSION: NOT_GIT — session written locally only; run `npx toga-ai@latest` to create a pushable clone'); return; }
|
|
948
948
|
const rel = 'knowledge/sessions/' + path.basename(abs);
|
|
949
949
|
gitRun(`git add "${rel}"`);
|
|
950
950
|
reportSession(pushStaged({ msg: args.msg || ('session: ' + data.slug), branch: args.branch, restrictPrefix: 'knowledge/sessions/' }), args.branch);
|
|
@@ -1032,7 +1032,7 @@ function cmdSkills(args) {
|
|
|
1032
1032
|
if (args.json) { console.log(JSON.stringify(rows)); return; }
|
|
1033
1033
|
console.log(padTo('STATUS', 11) + ' ' + padTo('SKILL', 22) + ' DESCRIPTION');
|
|
1034
1034
|
for (const r of rows) console.log(padTo(r.status, 11) + ' ' + padTo(clip(r.skill, 22), 22) + ' ' + clip(r.description, 90));
|
|
1035
|
-
if (installed && rows.some(r => r.status === 'available')) console.log('\nRun npx toga-ai to install the "available" skills.');
|
|
1035
|
+
if (installed && rows.some(r => r.status === 'available')) console.log('\nRun npx toga-ai@latest to install the "available" skills.');
|
|
1036
1036
|
}
|
|
1037
1037
|
|
|
1038
1038
|
/* ------------------------------------------------------------------ */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toga-ai",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1023",
|
|
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",
|
|
@@ -28,8 +28,7 @@
|
|
|
28
28
|
"scripts": {
|
|
29
29
|
"postinstall": "node scripts/install.js --post-install",
|
|
30
30
|
"validate": "node knowledge.js validate",
|
|
31
|
-
"index": "node knowledge.js index"
|
|
32
|
-
"sync": "node sync-skills.js"
|
|
31
|
+
"index": "node knowledge.js index"
|
|
33
32
|
},
|
|
34
33
|
"files": [
|
|
35
34
|
"agents/",
|
|
@@ -42,7 +41,6 @@
|
|
|
42
41
|
".claude/settings.json",
|
|
43
42
|
"knowledge/",
|
|
44
43
|
"knowledge.js",
|
|
45
|
-
"sync-skills.js",
|
|
46
44
|
"CLAUDE.md"
|
|
47
45
|
],
|
|
48
46
|
"main": "knowledge.js",
|
|
@@ -108,7 +108,7 @@ back to dense wording and teammates missed critical details. To stop that, a hoo
|
|
|
108
108
|
- `scripts/hooks/plain-talk.js` runs on **every** developer turn (`UserPromptSubmit`). It
|
|
109
109
|
injects a short reminder of these rules — plus the "mirror the person" instruction — into
|
|
110
110
|
context right before each reply. So the model is reminded every turn, not just once.
|
|
111
|
-
- The hook is wired in `.claude/settings.json` and ships to every repo on `npx toga-ai`.
|
|
111
|
+
- The hook is wired in `.claude/settings.json` and ships to every repo on `npx toga-ai@latest`.
|
|
112
112
|
- It never blocks anything; it only nudges. To silence it, set `TOGA_PLAIN_TALK_DISABLED=1`.
|
|
113
113
|
|
|
114
114
|
## Remember each person across sessions — style AND how they like to work
|
package/scripts/harness.js
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Commands:
|
|
7
7
|
* audit Validate knowledge base, check completeness, output score 0-100
|
|
8
|
-
* sync [path] Sync skills into a project's .claude/skills/
|
|
9
8
|
* status Show knowledge base statistics and last git commit info
|
|
10
9
|
* new-repo Interactive wizard to onboard a new repo into registry.json
|
|
11
10
|
*
|
|
@@ -332,27 +331,6 @@ function cmdAudit() {
|
|
|
332
331
|
process.exitCode = score < 60 ? 1 : 0;
|
|
333
332
|
}
|
|
334
333
|
|
|
335
|
-
/* ------------------------------------------------------------------ */
|
|
336
|
-
/* command: sync */
|
|
337
|
-
/* ------------------------------------------------------------------ */
|
|
338
|
-
|
|
339
|
-
function cmdSync(args) {
|
|
340
|
-
const projectPath = args[0] || null;
|
|
341
|
-
const syncScript = path.join(REPO_ROOT, 'sync-skills.js');
|
|
342
|
-
if (!fs.existsSync(syncScript)) {
|
|
343
|
-
console.error('ERROR: sync-skills.js not found at ' + syncScript);
|
|
344
|
-
process.exit(1);
|
|
345
|
-
}
|
|
346
|
-
const spawnArgs = [syncScript];
|
|
347
|
-
if (projectPath) spawnArgs.push(projectPath);
|
|
348
|
-
const result = spawnSync('node', spawnArgs, {
|
|
349
|
-
cwd: process.cwd(),
|
|
350
|
-
stdio: 'inherit',
|
|
351
|
-
encoding: 'utf8'
|
|
352
|
-
});
|
|
353
|
-
process.exitCode = result.status || 0;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
334
|
/* ------------------------------------------------------------------ */
|
|
357
335
|
/* command: status */
|
|
358
336
|
/* ------------------------------------------------------------------ */
|
|
@@ -576,9 +554,6 @@ async function main() {
|
|
|
576
554
|
case 'audit':
|
|
577
555
|
cmdAudit();
|
|
578
556
|
break;
|
|
579
|
-
case 'sync':
|
|
580
|
-
cmdSync(rest);
|
|
581
|
-
break;
|
|
582
557
|
case 'status':
|
|
583
558
|
cmdStatus();
|
|
584
559
|
break;
|
|
@@ -592,7 +567,6 @@ async function main() {
|
|
|
592
567
|
console.log('');
|
|
593
568
|
console.log('Commands:');
|
|
594
569
|
console.log(' audit Validate + completeness check, score 0-100');
|
|
595
|
-
console.log(' sync [path] Sync skills into a project .claude/skills/');
|
|
596
570
|
console.log(' status Show knowledge base statistics');
|
|
597
571
|
console.log(' new-repo Interactive wizard to onboard a new repo');
|
|
598
572
|
process.exitCode = 1;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
* harness-version-check.js — SessionStart: warn when the installed TOGA harness is stale.
|
|
6
|
+
*
|
|
7
|
+
* Reads <project>/.claude/toga-ai.version and compares it with the latest toga-ai on npm.
|
|
8
|
+
* The npm answer is cached in ~/.claude/toga-ai-latest.json for 1 hour. A failed lookup
|
|
9
|
+
* is cached too, but only for 10 minutes, so an offline machine does not retry on every
|
|
10
|
+
* launch and still recovers soon. A cache dated in the future (clock skew) counts as expired.
|
|
11
|
+
*
|
|
12
|
+
* Warn only: never writes .claude project files, never blocks, always exits 0.
|
|
13
|
+
* Opt out: TOGA_VERSION_CHECK_DISABLED=1.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const CACHE_TTL_MS = 60 * 60 * 1000;
|
|
21
|
+
const FAILED_CACHE_TTL_MS = 10 * 60 * 1000;
|
|
22
|
+
const CACHE_FILE = process.env.TOGA_VERSION_CACHE_FILE || path.join(os.homedir(), '.claude', 'toga-ai-latest.json');
|
|
23
|
+
|
|
24
|
+
function readLocalVersion(projectDir) {
|
|
25
|
+
try {
|
|
26
|
+
return fs.readFileSync(path.join(projectDir, '.claude', 'toga-ai.version'), 'utf8').trim();
|
|
27
|
+
} catch (e) { return null; } // no version file = not installed by toga-ai; nothing to check
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Cached latest version (null when the last lookup failed), or undefined when missing/expired.
|
|
31
|
+
function readCache() {
|
|
32
|
+
try {
|
|
33
|
+
const data = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
|
34
|
+
if (typeof data.checkedAt !== 'number') return undefined;
|
|
35
|
+
const latest = data.latest || null;
|
|
36
|
+
const age = Date.now() - data.checkedAt;
|
|
37
|
+
const ttl = latest ? CACHE_TTL_MS : FAILED_CACHE_TTL_MS;
|
|
38
|
+
if (age < 0 || age > ttl) return undefined;
|
|
39
|
+
return latest;
|
|
40
|
+
} catch (e) { return undefined; } // missing or corrupt cache = treat as expired and re-check
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function writeCache(latest) {
|
|
44
|
+
try {
|
|
45
|
+
fs.mkdirSync(path.dirname(CACHE_FILE), { recursive: true });
|
|
46
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify({ latest, checkedAt: Date.now() }) + '\n');
|
|
47
|
+
} catch (e) { /* cache is best effort */ }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function main() {
|
|
51
|
+
if (process.env.TOGA_VERSION_CHECK_DISABLED === '1') return;
|
|
52
|
+
const { getLatestNpmVersion, parseVersion, semverLt } = require('./lib/npm-latest');
|
|
53
|
+
|
|
54
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
55
|
+
const local = readLocalVersion(projectDir);
|
|
56
|
+
if (!parseVersion(local)) return;
|
|
57
|
+
|
|
58
|
+
let latest = readCache();
|
|
59
|
+
if (latest === undefined) {
|
|
60
|
+
latest = getLatestNpmVersion();
|
|
61
|
+
writeCache(latest);
|
|
62
|
+
}
|
|
63
|
+
if (!latest || !semverLt(local, latest)) return;
|
|
64
|
+
|
|
65
|
+
const msg = 'Your TOGA harness is out of date (v' + local + ' -> v' + latest + '). Run: npx toga-ai@latest';
|
|
66
|
+
const out = {
|
|
67
|
+
hookEventName: 'SessionStart',
|
|
68
|
+
additionalContext: 'TOGA HARNESS UPDATE: ' + msg + '\nTell the developer this in your first reply.',
|
|
69
|
+
};
|
|
70
|
+
process.stdout.write(JSON.stringify({ systemMessage: msg, hookSpecificOutput: out }) + '\n');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
try { main(); } catch (e) { /* safe: a version warning must never block or fail a session */ }
|
|
74
|
+
process.exit(0);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* npm-latest.js — shared helpers for "what is the latest toga-ai on npm?".
|
|
5
|
+
* Used by scripts/install.js and hooks/harness-version-check.js.
|
|
6
|
+
* Ships in scripts/ and is copied to .claude/hooks/toga/lib/ with the other hooks.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const { spawnSync } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const PACKAGE_NAME = 'toga-ai';
|
|
12
|
+
const NPM_TIMEOUT_MS = 3000;
|
|
13
|
+
|
|
14
|
+
// Latest published version, or null on any failure (offline, npm missing, timeout).
|
|
15
|
+
function getLatestNpmVersion() {
|
|
16
|
+
try {
|
|
17
|
+
const isWin = process.platform === 'win32';
|
|
18
|
+
const result = spawnSync(isWin ? 'npm.cmd' : 'npm', ['view', PACKAGE_NAME, 'version', '--json'], {
|
|
19
|
+
stdio: 'pipe', shell: isWin, timeout: NPM_TIMEOUT_MS,
|
|
20
|
+
});
|
|
21
|
+
if (result.status !== 0 || !result.stdout) return null;
|
|
22
|
+
const version = result.stdout.toString().trim().replace(/"/g, '');
|
|
23
|
+
return parseVersion(version) ? version : null;
|
|
24
|
+
} catch (e) { return null; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// [major, minor, patch], or null when not a usable version (e.g. "unknown").
|
|
28
|
+
function parseVersion(v) {
|
|
29
|
+
if (typeof v !== 'string') return null;
|
|
30
|
+
const m = v.trim().match(/^v?(\d+)\.(\d+)\.(\d+)/);
|
|
31
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// True only when both versions parse and a < b. Unknown versions never compare.
|
|
35
|
+
function semverLt(a, b) {
|
|
36
|
+
const pa = parseVersion(a);
|
|
37
|
+
const pb = parseVersion(b);
|
|
38
|
+
if (!pa || !pb) return false;
|
|
39
|
+
for (let i = 0; i < 3; i++) {
|
|
40
|
+
if (pa[i] !== pb[i]) return pa[i] < pb[i];
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { getLatestNpmVersion, parseVersion, semverLt };
|
package/scripts/install.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 4. Install from chosen source into project .claude/
|
|
13
13
|
*
|
|
14
14
|
* Usage:
|
|
15
|
-
* npx toga-ai
|
|
15
|
+
* npx toga-ai@latest — auto-detect project, use bundle, update from git
|
|
16
16
|
* toga-ai — same (after global install)
|
|
17
17
|
* toga-ai --repo /path/to/claude — explicit repo path
|
|
18
18
|
* toga-ai --project /path/to/proj — explicit project path
|
|
@@ -50,24 +50,8 @@ function getBundleVersion() {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const result = spawnSync('npm', ['view', 'toga-ai', 'version', '--json'], {
|
|
56
|
-
stdio: 'pipe', shell: false, timeout: 5000,
|
|
57
|
-
});
|
|
58
|
-
if (result.status !== 0 || !result.stdout) return null;
|
|
59
|
-
return result.stdout.toString().trim().replace(/"/g, '');
|
|
60
|
-
} catch (e) { return null; }
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function semverLt(a, b) {
|
|
64
|
-
const parse = (v) => v.replace(/[^0-9.]/g, '').split('.').map(Number);
|
|
65
|
-
const [aMajor, aMinor, aPatch] = parse(a);
|
|
66
|
-
const [bMajor, bMinor, bPatch] = parse(b);
|
|
67
|
-
if (aMajor !== bMajor) return aMajor < bMajor;
|
|
68
|
-
if (aMinor !== bMinor) return aMinor < bMinor;
|
|
69
|
-
return aPatch < bPatch;
|
|
70
|
-
}
|
|
53
|
+
// Latest npm version + semver compare live in a shared helper the version-check hook uses too.
|
|
54
|
+
const { getLatestNpmVersion, semverLt } = require('./hooks/lib/npm-latest');
|
|
71
55
|
|
|
72
56
|
/* ------------------------------------------------------------------ */
|
|
73
57
|
/* repo discovery (git update source) */
|
|
@@ -436,7 +420,9 @@ const RETIRED_TOGA_HOOKS = ['kickoff-gate.js'];
|
|
|
436
420
|
|
|
437
421
|
// Permission rules the TOGA template once shipped and no longer wants. Only exact TOGA-owned
|
|
438
422
|
// strings go here — a developer's own rules are never removed.
|
|
439
|
-
const RETIRED_TOGA_PERMISSIONS = {
|
|
423
|
+
const RETIRED_TOGA_PERMISSIONS = { allow: [
|
|
424
|
+
'Bash(node sync-skills.js *)',
|
|
425
|
+
], ask: [
|
|
440
426
|
'mcp__clickup__clickup_create_task_comment',
|
|
441
427
|
'mcp__clickup__clickup_create_comment',
|
|
442
428
|
'mcp__clickup__clickup_update_task',
|
|
@@ -645,7 +631,7 @@ hook runs automatically and warns you of any frontmatter or schema errors.
|
|
|
645
631
|
|
|
646
632
|
Pull latest skills, agents, and knowledge from the team repo:
|
|
647
633
|
\`\`\`
|
|
648
|
-
npx toga-ai
|
|
634
|
+
npx toga-ai@latest
|
|
649
635
|
\`\`\`
|
|
650
636
|
`;
|
|
651
637
|
const block = CLAUDE_MD_BEGIN + '\n' + body.trimStart() + CLAUDE_MD_END + '\n';
|
|
@@ -963,7 +949,7 @@ function main() {
|
|
|
963
949
|
try {
|
|
964
950
|
const reg = JSON.parse(fs.readFileSync(registryDest, 'utf8'));
|
|
965
951
|
repoCount = Array.isArray(reg) ? reg.length : 0;
|
|
966
|
-
} catch (e) {}
|
|
952
|
+
} catch (e) { /* safe: count is display only; registry file was already copied */ }
|
|
967
953
|
console.log(' ✓ Registry (' + repoCount + ' repos): synced from ' + (knowledgeDir !== PACKAGE_ROOT ? 'git repo' : 'npm bundle'));
|
|
968
954
|
} catch (e) { errors.push('registry.json: ' + e.message); }
|
|
969
955
|
} else {
|
|
@@ -976,7 +962,7 @@ function main() {
|
|
|
976
962
|
console.log(' ✓ CLAUDE.md ' + action);
|
|
977
963
|
|
|
978
964
|
// Write version marker so /start can detect stale installs
|
|
979
|
-
try { fs.writeFileSync(path.join(claudeDir, 'toga-ai.version'), bundleVersion, 'utf8'); } catch (e) {}
|
|
965
|
+
try { fs.writeFileSync(path.join(claudeDir, 'toga-ai.version'), bundleVersion, 'utf8'); } catch (e) { /* safe: marker only feeds stale-install warnings; install itself succeeded */ }
|
|
980
966
|
|
|
981
967
|
// ECC global integration — detect and report
|
|
982
968
|
const eccPath = detectEcc();
|
|
@@ -1041,7 +1027,7 @@ function main() {
|
|
|
1041
1027
|
console.log(' UPDATING (get latest skills + team knowledge)');
|
|
1042
1028
|
console.log('─────────────────────────────────────────────');
|
|
1043
1029
|
console.log('');
|
|
1044
|
-
console.log(' npx toga-ai
|
|
1030
|
+
console.log(' npx toga-ai@latest ← run this any time to pull updates');
|
|
1045
1031
|
console.log('');
|
|
1046
1032
|
console.log('═════════════════════════════════════════════');
|
|
1047
1033
|
console.log('');
|
package/skills/finish/SKILL.md
CHANGED
|
@@ -86,7 +86,7 @@ subagent and let it do the heavy I/O in its own context.
|
|
|
86
86
|
|
|
87
87
|
Every `/finish` validates, indexes, then **pushes** `knowledge/` to the team git repo (only
|
|
88
88
|
`knowledge/` is ever staged — never source code). Teammates receive it on their next
|
|
89
|
-
`npx toga-ai` run (which git-pulls before installing). The push is **non-blocking**: if offline
|
|
89
|
+
`npx toga-ai@latest` run (which git-pulls before installing). The push is **non-blocking**: if offline
|
|
90
90
|
or unauthenticated, the capture still succeeds — report a manual-push reminder.
|
|
91
91
|
|
|
92
92
|
## Step 1 — Resolve TEAM_REPO and classify it
|
|
@@ -112,13 +112,13 @@ done
|
|
|
112
112
|
# 5. Ask the developer
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
**`~/toga-tech` must be probed first** — `npx toga-ai` runs `initLocalRepo` there, which
|
|
115
|
+
**`~/toga-tech` must be probed first** — `npx toga-ai@latest` runs `initLocalRepo` there, which
|
|
116
116
|
creates a proper git repo seeded from the bundle even when the remote is private.
|
|
117
117
|
|
|
118
118
|
**If `IS_GIT_CLONE` is false** (only `.claude/` bundle found): the subagent can still write
|
|
119
119
|
docs locally, but its publish reports `NOT_GIT` instead of pushing. Warn:
|
|
120
120
|
> "No git clone found at standard paths. Docs will be written locally only.
|
|
121
|
-
> Run `npx toga-ai` first (it creates `~/toga-tech` as a pushable git repo),
|
|
121
|
+
> Run `npx toga-ai@latest` first (it creates `~/toga-tech` as a pushable git repo),
|
|
122
122
|
> then re-run /finish to push to GitHub."
|
|
123
123
|
|
|
124
124
|
**Persist** `team-repo-path` to Claude memory only when `IS_GIT_CLONE` is true — never persist
|
|
@@ -33,7 +33,7 @@ for d in claude ../claude; do [ -f "$d/skills/migrate-github-org/migrate-github-
|
|
|
33
33
|
echo "${REPO:-NOT_FOUND}"
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
If `NOT_FOUND`, tell the user to run `npx toga-ai` first (or clone
|
|
36
|
+
If `NOT_FOUND`, tell the user to run `npx toga-ai@latest` first (or clone
|
|
37
37
|
`https://github.com/TOGATechnology/claude`), then retry.
|
|
38
38
|
|
|
39
39
|
## Step 2 — Run it against their workspace
|
|
@@ -90,7 +90,7 @@ The `<author>` suffix lets two developers use the same slug without collision.
|
|
|
90
90
|
**No git clone / TEAM_REPO unresolved →** fall back to the legacy local-only path
|
|
91
91
|
`~/.claude/session-data/YYYY-MM-DD-<slug>-session.md` (create the dir if needed), warn the
|
|
92
92
|
developer that the session was saved **locally only** (no team push possible without a
|
|
93
|
-
clone — `npx toga-ai` creates one), and skip Step 4.
|
|
93
|
+
clone — `npx toga-ai@latest` creates one), and skip Step 4.
|
|
94
94
|
|
|
95
95
|
---
|
|
96
96
|
|
package/skills/start/SKILL.md
CHANGED
|
@@ -183,6 +183,8 @@ Same method as the old /kickoff. Do it in this main session, no agents.
|
|
|
183
183
|
commands. If they differ, run `npx toga-ai@latest`, re-read the version file, and report.
|
|
184
184
|
If still stale: `npm cache clean --force`, then `npx toga-ai@latest` once more. If still
|
|
185
185
|
stale, warn and continue. Never report success unless the file equals the latest version.
|
|
186
|
+
The `harness-version-check.js` SessionStart hook also warns every session while the
|
|
187
|
+
harness is stale; this step is the fix, not a second warning.
|
|
186
188
|
2. **Team repo.** Resolve `<TEAM_REPO>` (memory `team-repo-path`, then env `CLAUDE_TEAM_REPO`,
|
|
187
189
|
then probe `~/toga-tech`, `~/claude`, `C:\WWW\claude`, `D:\WWW\claude` for
|
|
188
190
|
`knowledge/registry.json`, then `.claude/` as read-only last resort, then ask). Save a
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: team-skills
|
|
3
|
-
description: Lists every skill available in the team `claude` knowledge repo with its description, and marks which are installed in this project, available to pull, or local-only. Use when the developer says "what skills are available", "list team skills", "team skills", "show skills", "what can you do", or asks which team skills exist. Pairs with `npx toga-ai`, which installs them.
|
|
3
|
+
description: Lists every skill available in the team `claude` knowledge repo with its description, and marks which are installed in this project, available to pull, or local-only. Use when the developer says "what skills are available", "list team skills", "team skills", "show skills", "what can you do", or asks which team skills exist. Pairs with `npx toga-ai@latest`, which installs them.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Team Skills — discover what the team knowledge base offers
|
|
7
7
|
|
|
8
|
-
`npx toga-ai` *installs* skills but there's no way to *see* what exists first. This
|
|
8
|
+
`npx toga-ai@latest` *installs* skills but there's no way to *see* what exists first. This
|
|
9
9
|
skill renders an always-current listing straight from each skill's `SKILL.md` frontmatter
|
|
10
10
|
(the README skill table is hand-maintained and drifts — do not use it as the source).
|
|
11
11
|
|
|
@@ -29,7 +29,7 @@ node "<TEAM_REPO>/knowledge.js" skills --installed="<project-root>/.claude/skill
|
|
|
29
29
|
`.claude`). This prints a table `STATUS SKILL DESCRIPTION` where STATUS is one of:
|
|
30
30
|
|
|
31
31
|
- **installed** — present in this project's `.claude/skills/`; ready to use now.
|
|
32
|
-
- **available** — in the team repo but not installed here → run `npx toga-ai`.
|
|
32
|
+
- **available** — in the team repo but not installed here → run `npx toga-ai@latest`.
|
|
33
33
|
- **local-only** — installed locally but absent from the team repo (a personal/unpushed
|
|
34
34
|
skill, e.g. `worker2-action`). Flag it so the developer can decide whether to contribute it.
|
|
35
35
|
|
|
@@ -37,7 +37,7 @@ node "<TEAM_REPO>/knowledge.js" skills --installed="<project-root>/.claude/skill
|
|
|
37
37
|
|
|
38
38
|
Render the table grouped by status (installed first, then available, then local-only). Then:
|
|
39
39
|
|
|
40
|
-
- If any row is **available**, tell the developer: "Run `npx toga-ai` to install these."
|
|
40
|
+
- If any row is **available**, tell the developer: "Run `npx toga-ai@latest` to install these."
|
|
41
41
|
- If any row is **local-only**, note it: "`<skill>` exists only on this machine — if it's
|
|
42
42
|
useful to the team, add it to `<TEAM_REPO>/skills/` and push."
|
|
43
43
|
- Keep descriptions to one line each; the full skill prose lives in its `SKILL.md`.
|
|
@@ -45,6 +45,6 @@ Render the table grouped by status (installed first, then available, then local-
|
|
|
45
45
|
## Notes
|
|
46
46
|
|
|
47
47
|
- This is read-only discovery. It never installs or modifies skills — that's
|
|
48
|
-
`npx toga-ai`'s job.
|
|
49
|
-
- The canonical skill source is `<TEAM_REPO>/skills/*/SKILL.md` (what `
|
|
50
|
-
|
|
48
|
+
`npx toga-ai@latest`'s job.
|
|
49
|
+
- The canonical skill source is `<TEAM_REPO>/skills/*/SKILL.md` (what `npx toga-ai@latest`
|
|
50
|
+
installs from), not the repo's `.claude/skills/` (an untracked local install).
|
package/sync-skills.js
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Syncs skills from this repo's skills/ folder into a project's .claude/skills/.
|
|
5
|
-
*
|
|
6
|
-
* Source = <this script's dir>/skills (resolved via __dirname, so it works no
|
|
7
|
-
* matter where the claude repo is cloned — inside a project as `claude/`,
|
|
8
|
-
* or as a shared sibling repo like C:\WWW\claude used by many projects).
|
|
9
|
-
* Dest = the nearest ancestor of the current working directory that contains a
|
|
10
|
-
* `.claude` folder; falls back to <cwd>/.claude when none is found.
|
|
11
|
-
*
|
|
12
|
-
* Run from the target project's root: node <path-to>/sync-skills.js
|
|
13
|
-
* Optionally pass an explicit destination project root as the first argument:
|
|
14
|
-
* node C:\WWW\claude\sync-skills.js C:\WWW\2.0
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
const path = require('path');
|
|
19
|
-
|
|
20
|
-
function findProjectRoot(start) {
|
|
21
|
-
let dir = start;
|
|
22
|
-
while (true) {
|
|
23
|
-
if (fs.existsSync(path.join(dir, '.claude'))) return dir;
|
|
24
|
-
const parent = path.dirname(dir);
|
|
25
|
-
if (parent === dir) return start; // no .claude ancestor — default to where we started
|
|
26
|
-
dir = parent;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function copyDir(src, dest) {
|
|
31
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
32
|
-
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
33
|
-
const srcPath = path.join(src, entry.name);
|
|
34
|
-
const destPath = path.join(dest, entry.name);
|
|
35
|
-
if (entry.isDirectory()) {
|
|
36
|
-
copyDir(srcPath, destPath);
|
|
37
|
-
} else {
|
|
38
|
-
fs.copyFileSync(srcPath, destPath);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const src = path.join(__dirname, 'skills');
|
|
44
|
-
if (!fs.existsSync(src)) {
|
|
45
|
-
console.error(`No skills/ folder found next to this script (${src}).`);
|
|
46
|
-
process.exit(1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const projectRoot = process.argv[2]
|
|
50
|
-
? path.resolve(process.argv[2])
|
|
51
|
-
: findProjectRoot(process.cwd());
|
|
52
|
-
const dest = path.join(projectRoot, '.claude', 'skills');
|
|
53
|
-
|
|
54
|
-
console.log(`source: ${src}`);
|
|
55
|
-
console.log(`dest: ${dest}\n`);
|
|
56
|
-
|
|
57
|
-
const skills = fs.readdirSync(src, { withFileTypes: true })
|
|
58
|
-
.filter(e => e.isDirectory())
|
|
59
|
-
.map(e => e.name);
|
|
60
|
-
|
|
61
|
-
if (skills.length === 0) {
|
|
62
|
-
console.log('No skills found in skills/.');
|
|
63
|
-
process.exit(0);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
for (const skill of skills) {
|
|
67
|
-
copyDir(path.join(src, skill), path.join(dest, skill));
|
|
68
|
-
console.log(` synced: ${skill}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
console.log(`\n${skills.length} skill(s) synced to ${dest}`);
|