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.
@@ -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 `node sync-skills.js <project-path>`, then call them
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 — run the sync script to copy them into that directory.
95
+ are the source of truth — `npx toga-ai@latest` copies them into that directory.
96
96
 
97
- ## Syncing skills
97
+ ## Updating skills
98
98
 
99
- After pulling new skills from this repo, run:
99
+ Run this from the project root to install the latest skills, agents, rules, and knowledge:
100
100
 
101
101
  ```
102
- node claude/sync-skills.js
102
+ npx toga-ai@latest
103
103
  ```
104
104
 
105
- Works on Mac and PC. Run it from anywhere inside the project.
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. Run `node claude/sync-skills.js` to deploy it.
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-01
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, and how to test it locally.
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.1021",
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
@@ -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 };
@@ -12,7 +12,7 @@
12
12
  * 4. Install from chosen source into project .claude/
13
13
  *
14
14
  * Usage:
15
- * npx toga-ai — auto-detect project, use bundle, update from git
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
- function getLatestNpmVersion() {
54
- try {
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 = { ask: [
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 ← run this any time to pull updates');
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('');
@@ -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
 
@@ -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 `sync-skills.js`
50
- copies from), not the repo's `.claude/skills/` (an untracked local install).
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}`);