takomi 2.1.35 → 2.1.37

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.
Files changed (33) hide show
  1. package/.pi/README.md +6 -3
  2. package/.pi/extensions/oauth-router/commands.ts +36 -35
  3. package/.pi/extensions/oauth-router/index.ts +8 -8
  4. package/.pi/extensions/takomi-context-manager/diagnostics-tools.ts +40 -7
  5. package/.pi/extensions/takomi-context-manager/diagnostics.ts +534 -55
  6. package/.pi/extensions/takomi-context-manager/index.ts +14 -2
  7. package/.pi/extensions/takomi-context-manager/model-policy-gate.ts +16 -4
  8. package/.pi/extensions/takomi-context-manager/policy-tools.ts +7 -0
  9. package/.pi/extensions/takomi-context-manager/prerequisite-gates.ts +2 -0
  10. package/.pi/extensions/takomi-context-manager/session-state.ts +160 -0
  11. package/.pi/extensions/takomi-context-manager/skill-registry.ts +120 -0
  12. package/.pi/extensions/takomi-context-manager/skill-tools.ts +26 -3
  13. package/.pi/extensions/takomi-context-manager/state.ts +11 -1
  14. package/.pi/extensions/takomi-context-manager/types.ts +9 -1
  15. package/.pi/extensions/takomi-runtime/command-text.ts +2 -1
  16. package/.pi/extensions/takomi-runtime/commands.ts +13 -1
  17. package/.pi/extensions/takomi-runtime/index.ts +68 -1
  18. package/.pi/extensions/takomi-runtime/ui.ts +3 -3
  19. package/.pi/extensions/takomi-subagents/index.ts +37 -4
  20. package/.pi/extensions/takomi-subagents/native-render.ts +84 -7
  21. package/.pi/extensions/takomi-subagents/pi-subagents-engine.ts +18 -3
  22. package/.pi/extensions/takomi-subagents/tool-runner.ts +52 -5
  23. package/README.md +14 -7
  24. package/assets/.agent/skills/exam-creator-skill/SKILL.md +182 -0
  25. package/assets/.agent/skills/exam-creator-skill/references/model-routing-policy.md +48 -0
  26. package/assets/.agent/skills/exam-creator-skill/references/workflow-rulebook.md +115 -0
  27. package/package.json +2 -2
  28. package/src/cli.js +134 -13
  29. package/src/doctor.js +7 -1
  30. package/src/harness.js +54 -32
  31. package/src/owned-tree.js +28 -0
  32. package/src/pi-harness.js +149 -24
  33. package/src/store.js +2 -0
@@ -0,0 +1,48 @@
1
+ # Exam Creator Model Routing Policy
2
+
3
+ Use this routing policy for the exam-creation workflow unless the user overrides it.
4
+
5
+ ## Terminology Rule
6
+
7
+ When the user says **Gemini**, interpret that as **Gemini via the agy CLI** unless the user explicitly says otherwise.
8
+
9
+ ## Default Routing
10
+
11
+ - **Orchestrator:** parent assistant / orchestrator
12
+ - **Drafting agent:** **Gemini via agy CLI**
13
+ - **Marking agent:** **Gemini via agy CLI**
14
+ - **Verification agent:** **GPT**
15
+
16
+ ## Why This Is The Default
17
+
18
+ - Drafting and marking are the highest-volume steps.
19
+ - Gemini is preferred there because it is cheaper and faster.
20
+ - Verification is the quality gate, so GPT is preferred for the final correctness check.
21
+
22
+ ## Escalation Rule For Drafting
23
+
24
+ Upgrade the **drafting agent** from Gemini via agy CLI to **GPT 5.4 High** when any of these are true:
25
+
26
+ - the source notes are messy or inconsistent
27
+ - the subject requires tighter wording judgment
28
+ - ambiguity risk is high
29
+ - the diagram/question structure is more delicate than usual
30
+ - the paper is Mathematics or English and extra care is desired
31
+
32
+ ## Practical Modes
33
+
34
+ ### Default cheap/fast mode
35
+ - Orchestrator → parent assistant
36
+ - Drafting → Gemini via agy CLI
37
+ - Marking → Gemini via agy CLI
38
+ - Verification → GPT
39
+
40
+ ### Escalated drafting mode
41
+ - Orchestrator → parent assistant
42
+ - Drafting → GPT 5.4 High
43
+ - Marking → Gemini via agy CLI
44
+ - Verification → GPT
45
+
46
+ ## Guiding Principle
47
+
48
+ Use **Gemini via agy CLI for drafting and marking by default**, **GPT for verification**, and only upgrade drafting to **GPT 5.4 High** when the paper is tricky enough to justify the extra cost.
@@ -0,0 +1,115 @@
1
+ # OBHIS Exam Workflow Rulebook
2
+
3
+ This reference captures the current house style agreed during the Mathematics paper workflow.
4
+
5
+ ## Scope
6
+
7
+ Use this rulebook for OBHIS / Olive Blessed Crest Academy exam generation unless the user gives a newer rule.
8
+
9
+ ## Terminology Rule
10
+
11
+ When the user says **Gemini**, interpret that as **Gemini via the agy CLI** unless the user explicitly says otherwise.
12
+
13
+ ## Workflow Summary
14
+
15
+ 1. Gather source materials.
16
+ 2. For photographed handwritten pages, run a multimodal-first extraction pass before OCR:
17
+ - use direct model vision on the image files
18
+ - batch images in small ordered groups, usually 3-5 images per model task
19
+ - explicitly forbid OCR/Tesseract/Python preprocessing on the first pass
20
+ - use OCR only as fallback when direct vision is unavailable or fails for a specific page
21
+ 3. Confirm subject-specific mark pattern.
22
+ 4. Create clean folder structure.
23
+ 5. Draft question paper.
24
+ 6. Build marking scheme only when requested.
25
+ 7. Independently verify.
26
+ 8. Incorporate user feedback.
27
+ 9. Rerender deliverables.
28
+
29
+ ## Folder Structure
30
+
31
+ Preferred output structure:
32
+
33
+ - `Questions/`
34
+ - `questions.md`
35
+ - final exam `.docx`
36
+ - `assets/`
37
+ - all SVG diagrams
38
+ - all PNG diagram copies used for Word export
39
+ - `Marking Scheme/` only when requested
40
+ - `marking-scheme.md`
41
+ - `objective-validation.md`
42
+ - `verification-report.md`
43
+ - marking-scheme `.docx` if needed
44
+
45
+ ## Subject Rules
46
+
47
+ ### Mathematics and English
48
+
49
+ Default pattern:
50
+
51
+ - exam total = **40 marks**
52
+ - objective count = **50**
53
+ - objective marks = **0.5 each**
54
+ - objective total = **25 marks**
55
+ - theory questions = **5**
56
+ - answer any = **3**
57
+ - theory total = **15 marks**
58
+ - no separate short-answer section by default
59
+
60
+ ### Other Subjects
61
+
62
+ Current preference:
63
+
64
+ - may still use open-ended and theory sections
65
+ - still intended to fit the 40-mark exam system
66
+ - exact split should be clarified before final generation if not explicitly stated
67
+
68
+ ## Layout Rules
69
+
70
+ - use **Times New Roman**
71
+ - default body size **12 pt**
72
+ - narrow margins
73
+ - compact layout
74
+ - header must be 1-column and should include: school name, academic session, term/exam title, pupil name/date, examination number/time, class, arm, subject, and instructions
75
+ - body is **1-column by default**
76
+ - use 2-column/3-column body only when a dense objective paper genuinely needs it for fit; do not apply columns mechanically to every subject
77
+ - headings should be bold and black, not oversized
78
+ - objective options should appear inline or compactly, not stacked unless unavoidable
79
+ - remove `Total Marks` line unless requested
80
+ - try to keep each paper around **2 pages** where practical
81
+
82
+ ## Diagram Rules
83
+
84
+ - store diagrams in `Questions/assets/`
85
+ - simple line/geometry diagrams: prefer SVG masters plus PNG export copies
86
+ - pictorial nursery/primary objects (mango, pot, bucket, moon, table, cup, animals, classroom objects, etc.): prefer Anti-Gravity image generation or another image-generation path instead of rough SVG-only drawings
87
+ - Anti-Gravity can generate images; when using it, instruct whether to generate images, use SVG, or use a hybrid method, and require a report of the method used
88
+ - do not include answer labels from the teacher’s source in the student-facing diagram, e.g. do not label the keyboard as “Keyboard” when the question asks pupils to identify it
89
+ - keep separate source drawings as separate assets unless the question explicitly presents them as one grouped diagram; this helps preserve order and layout
90
+ - for diagram-heavy papers, rebuild the draft directly from the original tagged images instead of relying only on extraction summaries
91
+ - do not show worked/grouped answers in division or counting diagrams when the child is supposed to solve them
92
+ - if pupils are asked to colour an object or flag, leave it as an outline unless the exam explicitly asks for a sample
93
+ - labels must be readable when labels are part of the prompt rather than the answer
94
+ - angle values must not be hidden by edges
95
+ - move text away from lines when needed
96
+ - rerender diagrams if the user reports visibility problems
97
+
98
+ ## Review Rules
99
+
100
+ - objective answer validation is mandatory when a marking scheme or answer key is requested
101
+ - marking scheme must match final numbering when requested
102
+ - verification should independently confirm correctness
103
+ - for handwritten sources, infer unclear text aggressively from context before marking it unclear
104
+ - any remaining ambiguous handwriting must be captured in a user-review list with image filename/page reference, question number, and candidate interpretations
105
+ - if a wording ambiguity is found, fix the paper and then realign the scheme when a scheme exists
106
+
107
+ ## Feedback Rules
108
+
109
+ - user feedback overrides previous assumptions
110
+ - update future papers to follow the corrected rule
111
+ - if a DOCX is locked, save a versioned file such as `v2`
112
+
113
+ ## Notes
114
+
115
+ This rulebook is intentionally opinionated because it represents Johno's workflow. Keep it flexible enough to ask follow-up questions whenever a new subject or mark pattern does not clearly fit the stored defaults.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "takomi",
3
- "version": "2.1.35",
3
+ "version": "2.1.37",
4
4
  "description": "🎯 Stop wrestling with AI. Start building with purpose. The artisan's toolkit for agent workflows, Codex skills, and original Takomi capabilities like 21st.dev integration.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,7 +50,7 @@
50
50
  "commander": "^13.1.0",
51
51
  "figlet": "^1.8.0",
52
52
  "fs-extra": "^11.3.0",
53
- "pi-subagents": "0.28.0",
53
+ "pi-subagents": "0.31.0",
54
54
  "picocolors": "^1.1.1",
55
55
  "prompts": "^2.4.2"
56
56
  },
package/src/cli.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  downloadDirectoryFromGitHub,
24
24
  } from './utils.js';
25
25
  import {
26
+ HARNESS_MAP,
26
27
  detectHarnesses,
27
28
  printHarnessStatus,
28
29
  syncToAllHarnesses,
@@ -43,7 +44,7 @@ import {
43
44
  isStoreInitialized,
44
45
  } from './store.js';
45
46
  import { runDoctor } from './doctor.js';
46
- import { ensurePiInstalled, ensurePiSubagentsInstalled, launchTakomiHarness, printPiInstallResult, printPiSubagentsInstallResult, updatePiCliPackage, updatePiManagedPackages, printPiManagedPackageUpdateResult } from './pi-harness.js';
47
+ import { disableRawPiSubagentsActivation, ensurePiInstalled, ensurePiSubagentsInstalled, formatRawPiSubagentsDisableActions, formatRawPiSubagentsFindings, inspectRawPiSubagentsActivation, launchTakomiHarness, printPiInstallResult, printPiSubagentsInstallResult, updatePiCliPackage, updatePiManagedPackages, printPiManagedPackageUpdateResult } from './pi-harness.js';
47
48
  import { installPiHarnessAssets, printPiInstallSummary, syncPiHarnessAssets, validatePiHarnessInstall } from './pi-installer.js';
48
49
  import { offerPiOptionalFeatures } from './pi-optional-features.js';
49
50
  import {
@@ -393,6 +394,43 @@ async function promptSkillsInstallSelection() {
393
394
  return { mode: response.mode, selectedSkills };
394
395
  }
395
396
 
397
+ function normalizeHarnessSyncMode(value) {
398
+ return ['copy', 'symlink', 'auto'].includes(value) ? value : 'copy';
399
+ }
400
+
401
+ async function promptHarnessSyncMode(initial = 'auto') {
402
+ const normalizedInitial = normalizeHarnessSyncMode(initial);
403
+ const choices = [
404
+ { title: 'Auto (try symlink, copy if blocked)', value: 'auto', description: 'Best fallback when Windows permissions or filesystems block symlinks.' },
405
+ { title: 'Symlink / Junction (single source of truth)', value: 'symlink', description: 'Each managed skill/workflow points back to ~/.takomi; edits update one canonical copy.' },
406
+ { title: 'Copy (independent files)', value: 'copy', description: 'Most compatible; changes do not flow back to ~/.takomi.' },
407
+ ];
408
+ const response = await prompts({
409
+ type: 'select',
410
+ name: 'syncMode',
411
+ message: 'How should Takomi sync store resources into harness folders?',
412
+ choices,
413
+ initial: Math.max(0, choices.findIndex((choice) => choice.value === normalizedInitial)),
414
+ });
415
+ if (!response.syncMode) return null;
416
+ return normalizeHarnessSyncMode(response.syncMode);
417
+ }
418
+
419
+ function collectSyncFailures(syncSummary) {
420
+ return Object.entries(syncSummary || {})
421
+ .filter(([, result]) => result?.ok === false)
422
+ .map(([id, result]) => ({ id, errors: result.errors?.length ? result.errors : ['unknown sync error'] }));
423
+ }
424
+
425
+ function printSyncFailures(failures) {
426
+ if (!failures.length) return;
427
+ console.log(pc.yellow('\nSome harnesses did not fully sync:'));
428
+ for (const failure of failures) {
429
+ console.log(pc.dim(` - ${failure.id}: ${failure.errors.join('; ')}`));
430
+ }
431
+ process.exitCode = 1;
432
+ }
433
+
396
434
  async function installSkillsTarget() {
397
435
  console.log(pc.magenta('🧰 Takomi Skills Install\n'));
398
436
  try {
@@ -411,7 +449,7 @@ async function installSkillsTarget() {
411
449
  const result = await installBundledSkills(program.version(), selection);
412
450
  const validation = await validateSkillsInstall(selection.selectedSkills);
413
451
  printSkillsInstallSummary(result, validation);
414
- console.log(pc.dim('\nGlobal skills are ready for Pi or other supported harnesses.\n'));
452
+ console.log(pc.dim('\nShared skills target is ready. For per-harness global paths, run "takomi setup" or "takomi sync".\n'));
415
453
  } catch (error) {
416
454
  console.log(pc.red('\nSkills install failed.'));
417
455
  console.log(pc.dim(String(error?.message || error)));
@@ -423,6 +461,37 @@ async function installAllTargets(options = {}) {
423
461
  await installSkillsTarget();
424
462
  }
425
463
 
464
+ async function offerRawPiSubagentsHardening({ interactive = process.stdin.isTTY && process.stdout.isTTY } = {}) {
465
+ const audit = await inspectRawPiSubagentsActivation({ cwd: process.cwd() });
466
+ if (!audit.findings.length) return;
467
+
468
+ console.log(pc.yellow('\n⚠ Raw Pi subagents activation detected'));
469
+ console.log(pc.dim(formatRawPiSubagentsFindings(audit.findings)));
470
+ console.log(pc.dim('\nTakomi keeps pi-subagents installed as an internal module, but raw Pi activation exposes the separate `subagent` tool next to `takomi_subagent`.'));
471
+
472
+ let shouldDisable = process.env.TAKOMI_DISABLE_RAW_PI_SUBAGENTS === '1';
473
+ if (interactive && process.env.TAKOMI_DISABLE_RAW_PI_SUBAGENTS !== '1') {
474
+ const response = await prompts({
475
+ type: 'confirm',
476
+ name: 'disable',
477
+ message: 'Disable raw Pi subagents activation so agents only see takomi_subagent?',
478
+ initial: true,
479
+ });
480
+ shouldDisable = response.disable === true;
481
+ }
482
+
483
+ if (!shouldDisable) {
484
+ console.log(pc.yellow(interactive
485
+ ? 'Leaving raw Pi subagents activation unchanged.'
486
+ : 'Non-interactive run: leaving raw Pi subagents activation unchanged. Set TAKOMI_DISABLE_RAW_PI_SUBAGENTS=1 to disable automatically.'));
487
+ return;
488
+ }
489
+
490
+ const result = await disableRawPiSubagentsActivation({ cwd: process.cwd() });
491
+ console.log(pc.green('✔ Disabled raw Pi subagents activation'));
492
+ console.log(pc.dim(formatRawPiSubagentsDisableActions(result.actions)));
493
+ }
494
+
426
495
  async function installPiTarget(options = {}) {
427
496
  const { offerOptionalFeatures = true } = options;
428
497
  console.log(pc.magenta('🧭 Pi Harness Preflight\n'));
@@ -455,12 +524,14 @@ async function installPiTarget(options = {}) {
455
524
  const result = await installPiHarnessAssets(program.version());
456
525
  const validation = await validatePiHarnessInstall();
457
526
  printPiInstallSummary(result, validation);
527
+ await offerRawPiSubagentsHardening();
458
528
  if (offerOptionalFeatures) {
459
529
  console.log(pc.cyan('\n🧩 Optional Takomi Pi feature packs...\n'));
460
530
  await offerPiOptionalFeatures({ interactive: true });
461
531
  }
462
532
  console.log(pc.cyan('\n📦 Checking Pi-managed extension package updates...\n'));
463
533
  printPiManagedPackageUpdateResult(await updatePiManagedPackages());
534
+ await offerRawPiSubagentsHardening();
464
535
  console.log(pc.dim('\nNext: cd <project> && takomi\n'));
465
536
  } catch (error) {
466
537
  console.log(pc.red('\nPi harness asset install failed.'));
@@ -474,8 +545,10 @@ async function installPiOptionalFeaturesTarget() {
474
545
  printPiInstallResult(pi);
475
546
  if (!pi.ok) return;
476
547
  await offerPiOptionalFeatures({ interactive: true });
548
+ await offerRawPiSubagentsHardening();
477
549
  console.log(pc.cyan('\n📦 Checking Pi-managed extension package updates...\n'));
478
550
  printPiManagedPackageUpdateResult(await updatePiManagedPackages());
551
+ await offerRawPiSubagentsHardening();
479
552
  }
480
553
 
481
554
  async function syncPiTarget() {
@@ -484,8 +557,10 @@ async function syncPiTarget() {
484
557
  const result = await syncPiHarnessAssets(program.version());
485
558
  const validation = await validatePiHarnessInstall();
486
559
  printPiInstallSummary(result, validation);
560
+ await offerRawPiSubagentsHardening();
487
561
  console.log(pc.cyan('\n📦 Checking Pi-managed extension package updates...\n'));
488
562
  printPiManagedPackageUpdateResult(await updatePiManagedPackages());
563
+ await offerRawPiSubagentsHardening();
489
564
  } catch (error) {
490
565
  console.log(pc.red('\nPi sync failed.'));
491
566
  console.log(pc.dim(String(error?.message || error)));
@@ -622,7 +697,7 @@ async function install(target) {
622
697
 
623
698
  if (detected.length === 0) {
624
699
  console.log(pc.yellow(' No AI harnesses detected on this machine.'));
625
- console.log(pc.dim(' We support: Antigravity, KiloCode, Windsurf, Codex, Cursor, Gemini CLI'));
700
+ console.log(pc.dim(` We support: ${Object.values(HARNESS_MAP).map(h => h.name).join(', ')}`));
626
701
  console.log(pc.dim(' Run "takomi init" instead for per-project setup.\n'));
627
702
  return;
628
703
  }
@@ -715,6 +790,11 @@ async function install(target) {
715
790
 
716
791
  if (!contentResponse.content) return;
717
792
 
793
+ const syncMode = await promptHarnessSyncMode(
794
+ (hasExistingStoreSkills || hasExistingStoreWorkflows) ? existingStoreManifest.syncMode : 'auto'
795
+ );
796
+ if (!syncMode) return;
797
+
718
798
  try {
719
799
  // 4. Initialize global store
720
800
  console.log(pc.cyan(`\n📦 Creating global store (${STORE_PATH})...\n`));
@@ -802,11 +882,15 @@ async function install(target) {
802
882
  const syncSummary = await syncToAllHarnesses(selectedHarnesses, STORE_PATH, {
803
883
  useOwnership: true,
804
884
  owned: manifest.harnessOwned,
885
+ linkMode: syncMode,
805
886
  });
806
887
 
807
888
  // 7. Update manifest
889
+ const syncFailures = collectSyncFailures(syncSummary);
890
+ const successfulHarnesses = selectedHarnesses.filter(h => !syncFailures.some(failure => failure.id === h.id));
808
891
  manifest = await getManifest();
809
- manifest.linkedHarnesses = selectedHarnesses.map(h => h.id);
892
+ manifest.linkedHarnesses = successfulHarnesses.map(h => h.id);
893
+ manifest.syncMode = syncMode;
810
894
  manifest.installed.skills = await getStoreSkills();
811
895
  manifest.installed.workflows = await getStoreWorkflows();
812
896
  manifest.harnessOwned = manifest.harnessOwned || {};
@@ -814,11 +898,13 @@ async function install(target) {
814
898
  manifest.harnessOwned[harness.id] = syncSummary[harness.id]?.owned || manifest.harnessOwned[harness.id] || {};
815
899
  }
816
900
  await writeManifest(manifest);
901
+ printSyncFailures(syncFailures);
817
902
 
818
903
  // 8. Summary
819
- console.log(pc.magenta('\n✨ Your command center is live. ✨'));
904
+ console.log(pc.magenta(syncFailures.length ? '\n⚠ Your command center is partially synced. ⚠' : '\n✨ Your command center is live. ✨'));
820
905
  console.log(pc.white(`\n Store: ${STORE_PATH}`));
821
- console.log(pc.white(` Connected: ${selectedHarnesses.map(h => h.name).join(', ')}`));
906
+ console.log(pc.white(` Sync mode: ${syncMode}`));
907
+ console.log(pc.white(` Connected: ${successfulHarnesses.map(h => h.name).join(', ') || 'none'}`));
822
908
  console.log(pc.dim(`\n Add skills: takomi add <github-url>`));
823
909
  console.log(pc.dim(` Sync updates: takomi sync\n`));
824
910
 
@@ -882,23 +968,33 @@ async function sync(target) {
882
968
 
883
969
  const selectedHarnesses = detected.filter(h => response.harnesses.includes(h.id));
884
970
 
885
- console.log(pc.cyan('\n📡 Syncing from global store...\n'));
971
+ const envSyncMode = process.env.TAKOMI_HARNESS_SYNC_MODE;
972
+ const syncMode = normalizeHarnessSyncMode(envSyncMode || manifest.syncMode || 'copy');
973
+ console.log(pc.cyan(`\n📡 Syncing from global store (${syncMode})...\n`));
886
974
  const syncSummary = await syncToAllHarnesses(selectedHarnesses, STORE_PATH, {
887
975
  useOwnership: true,
888
976
  owned: manifest.harnessOwned,
977
+ linkMode: syncMode,
889
978
  });
890
979
 
891
980
  // Update manifest with current harnesses
892
- manifest.linkedHarnesses = [...new Set([...manifest.linkedHarnesses, ...response.harnesses])];
981
+ const syncFailures = collectSyncFailures(syncSummary);
982
+ const successfulHarnessIds = response.harnesses.filter(id => !syncFailures.some(failure => failure.id === id));
983
+ manifest.linkedHarnesses = [...new Set([...manifest.linkedHarnesses, ...successfulHarnessIds])];
984
+ if (!envSyncMode) manifest.syncMode = syncMode;
893
985
  manifest.harnessOwned = manifest.harnessOwned || {};
894
986
  for (const harness of selectedHarnesses) {
895
987
  manifest.harnessOwned[harness.id] = syncSummary[harness.id]?.owned || manifest.harnessOwned[harness.id] || {};
896
988
  }
897
989
  await writeManifest(manifest);
990
+ printSyncFailures(syncFailures);
898
991
 
899
992
  const skills = await getStoreSkills();
900
993
  const workflows = await getStoreWorkflows();
901
- console.log(pc.magenta(`\n✨ ${skills.length} skills and ${workflows.length} workflows synced to ${selectedHarnesses.length} IDE(s). Ready to build.\n`));
994
+ const syncedCount = successfulHarnessIds.length;
995
+ console.log(pc.magenta(syncFailures.length
996
+ ? `\n⚠ ${skills.length} skills and ${workflows.length} workflows partially synced to ${syncedCount}/${selectedHarnesses.length} IDE(s).\n`
997
+ : `\n✨ ${skills.length} skills and ${workflows.length} workflows synced to ${syncedCount} IDE(s). Ready to build.\n`));
902
998
  }
903
999
 
904
1000
  // ─────────────────────────────────────────────────────────────────────────────
@@ -994,8 +1090,21 @@ async function add(url) {
994
1090
  if (syncResponse.doSync) {
995
1091
  const detected = detectHarnesses();
996
1092
  const linked = detected.filter(h => manifest.linkedHarnesses.includes(h.id));
997
- await syncToAllHarnesses(linked, STORE_PATH);
998
- console.log(pc.magenta(`\n✨ Live on ${linked.length} IDE(s). You're armed and ready.\n`));
1093
+ const syncSummary = await syncToAllHarnesses(linked, STORE_PATH, {
1094
+ useOwnership: true,
1095
+ owned: manifest.harnessOwned,
1096
+ linkMode: normalizeHarnessSyncMode(process.env.TAKOMI_HARNESS_SYNC_MODE || manifest.syncMode || 'copy'),
1097
+ });
1098
+ const syncFailures = collectSyncFailures(syncSummary);
1099
+ manifest.harnessOwned = manifest.harnessOwned || {};
1100
+ for (const harness of linked) {
1101
+ manifest.harnessOwned[harness.id] = syncSummary[harness.id]?.owned || manifest.harnessOwned[harness.id] || {};
1102
+ }
1103
+ await writeManifest(manifest);
1104
+ printSyncFailures(syncFailures);
1105
+ console.log(pc.magenta(syncFailures.length
1106
+ ? `\n⚠ Live on ${linked.length - syncFailures.length}/${linked.length} IDE(s); some harnesses need attention.\n`
1107
+ : `\n✨ Live on ${linked.length} IDE(s). You're armed and ready.\n`));
999
1108
  }
1000
1109
  } else {
1001
1110
  console.log(pc.dim('\n Run "takomi sync" when you want to push these to your IDEs.\n'));
@@ -1022,6 +1131,7 @@ async function harnesses() {
1022
1131
  console.log(pc.white(` Location: ${STORE_PATH}`));
1023
1132
  console.log(pc.white(` Skills: ${skills.length} ready to deploy`));
1024
1133
  console.log(pc.white(` Workflows: ${workflows.length} available`));
1134
+ console.log(pc.white(` Sync mode: ${manifest.syncMode || 'copy'}`));
1025
1135
  console.log(pc.white(` Connected: ${manifest.linkedHarnesses.join(', ') || 'none'}`));
1026
1136
  console.log(pc.dim(` Updated: ${manifest.updatedAt}\n`));
1027
1137
  } else {
@@ -1096,8 +1206,19 @@ async function updateProjectResources() {
1096
1206
  const detected = detectHarnesses();
1097
1207
  const linked = detected.filter(h => manifest.linkedHarnesses.includes(h.id));
1098
1208
  if (linked.length > 0) {
1099
- console.log(pc.cyan('\n📡 Auto-syncing to linked harnesses...\n'));
1100
- await syncToAllHarnesses(linked, STORE_PATH);
1209
+ const syncMode = normalizeHarnessSyncMode(process.env.TAKOMI_HARNESS_SYNC_MODE || manifest.syncMode || 'copy');
1210
+ console.log(pc.cyan(`\n📡 Auto-syncing to linked harnesses (${syncMode})...\n`));
1211
+ const syncSummary = await syncToAllHarnesses(linked, STORE_PATH, {
1212
+ useOwnership: true,
1213
+ owned: manifest.harnessOwned,
1214
+ linkMode: syncMode,
1215
+ });
1216
+ const syncFailures = collectSyncFailures(syncSummary);
1217
+ manifest.harnessOwned = manifest.harnessOwned || {};
1218
+ for (const harness of linked) {
1219
+ manifest.harnessOwned[harness.id] = syncSummary[harness.id]?.owned || manifest.harnessOwned[harness.id] || {};
1220
+ }
1221
+ printSyncFailures(syncFailures);
1101
1222
  }
1102
1223
  }
1103
1224
 
package/src/doctor.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import pc from 'picocolors';
4
- import { inspectPiHarnessEnvironment } from './pi-harness.js';
4
+ import { formatRawPiSubagentsFindings, inspectPiHarnessEnvironment } from './pi-harness.js';
5
5
  import { getStoreSkills, isStoreInitialized } from './store.js';
6
6
  import { PI_MANIFEST_PATH } from './pi-installer.js';
7
7
  import { SKILLS_MANIFEST_PATH, SKILLS_ROOT } from './skills-installer.js';
@@ -41,6 +41,7 @@ export async function runDoctor({ version, cwd = process.cwd() } = {}) {
41
41
  ? `${report.piSubagents.globalPackageJson}${report.piSubagents.globalVersion ? ` (${report.piSubagents.globalVersion})` : ''}`
42
42
  : 'missing'));
43
43
  console.log(status(report.piSubagents.binaryInstalled, 'pi-subagents installer binary', report.piSubagents.binaryPath || 'missing'));
44
+ console.log(status(report.rawPiSubagentsActivation.findings.length === 0, 'Raw Pi subagent tool hidden', report.rawPiSubagentsActivation.findings.length ? 'raw subagent activation detected' : 'yes'));
44
45
  console.log(status(report.installed.routingPolicyPresent || report.project.routingPolicyPresent, 'Routing policy', report.project.routingPolicyPresent ? report.project.targets.routingPolicy : report.installed.targets.routingPolicy));
45
46
  console.log(status(await fs.pathExists(SKILLS_ROOT), 'Installed skills root', SKILLS_ROOT));
46
47
  console.log(status(await fs.pathExists(SKILLS_MANIFEST_PATH), 'Skills install manifest', SKILLS_MANIFEST_PATH));
@@ -68,6 +69,11 @@ export async function runDoctor({ version, cwd = process.cwd() } = {}) {
68
69
  console.log(pc.white(' - Install pi-subagents: npm install -g pi-subagents'));
69
70
  }
70
71
 
72
+ if (report.rawPiSubagentsActivation.findings.length) {
73
+ console.log(pc.white(' - Disable raw Pi subagents so models only see takomi_subagent. Run takomi setup pi or set TAKOMI_DISABLE_RAW_PI_SUBAGENTS=1 takomi refresh pi.'));
74
+ console.log(pc.dim(formatRawPiSubagentsFindings(report.rawPiSubagentsActivation.findings).split(/\r?\n/).map((line) => ` ${line}`).join('\n')));
75
+ }
76
+
71
77
  if (!report.project.routingPolicyPresent && !report.installed.routingPolicyPresent) {
72
78
  console.log(pc.white(' - Add a routing policy at .pi/takomi/model-routing.md when ready.'));
73
79
  }
package/src/harness.js CHANGED
@@ -2,7 +2,7 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
4
  import pc from 'picocolors';
5
- import { hashPath, normalizeOwnedMap, copyOwnedTree } from './owned-tree.js';
5
+ import { hashPath, normalizeOwnedMap, materializeOwnedTree } from './owned-tree.js';
6
6
 
7
7
  // ─── Cross-Platform Home Directory ───────────────────────────────────────────
8
8
  const HOME = os.homedir();
@@ -34,13 +34,26 @@ function getAppData() {
34
34
  export const HARNESS_MAP = {
35
35
  antigravity: {
36
36
  name: 'Antigravity',
37
- rootPath: path.join(HOME, '.gemini', 'antigravity'),
37
+ rootPath: path.join(HOME, '.gemini', 'config'),
38
38
  detect() {
39
39
  return fs.existsSync(this.rootPath);
40
40
  },
41
41
  targets: {
42
- skills: path.join(HOME, '.gemini', 'antigravity', 'skills'),
43
- workflows: path.join(HOME, '.gemini', 'antigravity', 'global_workflows'),
42
+ skills: path.join(HOME, '.gemini', 'config', 'skills'),
43
+ workflows: path.join(HOME, '.gemini', 'config', 'global_workflows'),
44
+ yamls: null,
45
+ },
46
+ },
47
+
48
+ claude_code: {
49
+ name: 'Claude Code',
50
+ rootPath: path.join(HOME, '.claude'),
51
+ detect() {
52
+ return fs.existsSync(this.rootPath);
53
+ },
54
+ targets: {
55
+ skills: path.join(HOME, '.claude', 'skills'),
56
+ workflows: null,
44
57
  yamls: null,
45
58
  },
46
59
  },
@@ -78,7 +91,20 @@ export const HARNESS_MAP = {
78
91
  name: 'Codex',
79
92
  rootPath: path.join(HOME, '.codex'),
80
93
  detect() {
81
- return fs.existsSync(this.rootPath);
94
+ return fs.existsSync(this.rootPath) || fs.existsSync('/etc/codex');
95
+ },
96
+ targets: {
97
+ skills: path.join(HOME, '.codex', 'skills'),
98
+ workflows: null,
99
+ yamls: null,
100
+ },
101
+ },
102
+
103
+ pi: {
104
+ name: 'Pi / shared Agent Skills',
105
+ rootPath: path.join(HOME, '.pi'),
106
+ detect() {
107
+ return fs.existsSync(this.rootPath) || fs.existsSync(path.join(HOME, '.agents'));
82
108
  },
83
109
  targets: {
84
110
  skills: path.join(HOME, '.agents', 'skills'),
@@ -100,19 +126,7 @@ export const HARNESS_MAP = {
100
126
  },
101
127
  },
102
128
 
103
- gemini_cli: {
104
- name: 'Gemini CLI',
105
- rootPath: path.join(HOME, '.gemini'),
106
- detect() {
107
- // Only match bare Gemini CLI — not Antigravity (which also lives under .gemini)
108
- return fs.existsSync(this.rootPath) && !fs.existsSync(path.join(HOME, '.gemini', 'antigravity'));
109
- },
110
- targets: {
111
- skills: path.join(HOME, '.agents', 'skills'),
112
- workflows: null,
113
- yamls: null,
114
- },
115
- },
129
+
116
130
  };
117
131
 
118
132
  // ─── Detection ───────────────────────────────────────────────────────────────
@@ -163,17 +177,14 @@ export function printHarnessStatus(detected) {
163
177
 
164
178
  /**
165
179
  * Syncs a source directory to a harness target path.
166
- * Uses hard copy (fs.copy) no symlinks.
180
+ * Supports copy, symlink/junction, or auto-fallback materialization.
181
+ * When ownership is supplied, it also prunes previous Takomi-owned items
182
+ * that are no longer in the source while preserving manual or modified files.
167
183
  *
168
184
  * @param {string} sourcePath - Source directory (e.g., ~/.takomi/skills/)
169
- * @param {string} targetPath - Destination directory (e.g., ~/.gemini/antigravity/skills/)
185
+ * @param {string} targetPath - Destination directory (e.g., ~/.gemini/config/skills/)
170
186
  * @param {string} label - Human-readable label for logging
171
- * @returns {Promise<number>} - Number of items copied
172
- */
173
- /**
174
- * Syncs a directory to a target path. When ownership is supplied, it also
175
- * prunes previous Takomi-owned items that are no longer in the source while
176
- * preserving manual or modified files.
187
+ * @returns {Promise<number|object>} - Number of items materialized, or details
177
188
  */
178
189
  export async function syncDirectory(sourcePath, targetPath, label = '', options = {}) {
179
190
  if (!targetPath) return options.returnDetails ? { copied: 0, pruned: 0, preservedManual: [], preservedModified: [], owned: {} } : 0;
@@ -187,7 +198,10 @@ export async function syncDirectory(sourcePath, targetPath, label = '', options
187
198
  const nextOwned = {};
188
199
  const preservedManual = [];
189
200
  const preservedModified = [];
201
+ const symlinkFallbacks = [];
202
+ const linkMode = options.linkMode || process.env.TAKOMI_HARNESS_SYNC_MODE || 'copy';
190
203
  let copied = 0;
204
+ let linked = 0;
191
205
  let pruned = 0;
192
206
 
193
207
  if (options.prune && Object.keys(previousOwned).length > 0) {
@@ -225,29 +239,35 @@ export async function syncDirectory(sourcePath, targetPath, label = '', options
225
239
  await fs.remove(dest);
226
240
  }
227
241
 
228
- await copyOwnedTree(src, dest);
242
+ const materialized = await materializeOwnedTree(src, dest, { linkMode });
229
243
  nextOwned[item] = {
230
244
  hash: await hashPath(dest),
231
245
  targetPath: dest,
246
+ sourcePath: path.resolve(src),
247
+ syncMethod: materialized.method,
232
248
  syncedAt: new Date().toISOString(),
233
249
  };
234
- copied++;
250
+ if (materialized.method === 'symlink') linked++;
251
+ else copied++;
252
+ if (materialized.fallbackError) symlinkFallbacks.push(`${item}: ${materialized.fallbackError}`);
235
253
  }
236
254
 
237
255
  if (label) {
238
256
  const suffix = pruned ? `, removed ${pruned}` : '';
239
- console.log(pc.green(` ✔ ${label} (${copied} items${suffix})`));
257
+ const methodSummary = linked ? `${linked} linked, ${copied} copied` : `${copied} copied`;
258
+ console.log(pc.green(` ✔ ${label} (${methodSummary}${suffix})`));
240
259
  if (preservedManual.length) console.log(pc.yellow(` Preserved manual items: ${preservedManual.join(', ')}`));
241
260
  if (preservedModified.length) console.log(pc.yellow(` Preserved modified Takomi-owned items: ${preservedModified.join(', ')}`));
261
+ if (symlinkFallbacks.length) console.log(pc.yellow(` Symlink fallback copies: ${symlinkFallbacks.join('; ')}`));
242
262
  }
243
263
 
244
- const details = { copied, pruned, preservedManual, preservedModified, owned: Object.fromEntries(Object.entries(nextOwned).sort(([a], [b]) => a.localeCompare(b))) };
245
- return options.returnDetails ? details : copied;
264
+ const details = { copied: copied + linked, linked, copyCount: copied, pruned, preservedManual, preservedModified, symlinkFallbacks, owned: Object.fromEntries(Object.entries(nextOwned).sort(([a], [b]) => a.localeCompare(b))) };
265
+ return options.returnDetails ? details : copied + linked;
246
266
  } catch (error) {
247
267
  if (label) {
248
268
  console.log(pc.red(` ✗ ${label}: ${error.message}`));
249
269
  }
250
- return options.returnDetails ? { copied: 0, pruned: 0, preservedManual: [], preservedModified: [], owned: normalizeOwnedMap(options.owned), ok: false, error: error.message } : 0;
270
+ return options.returnDetails ? { copied: 0, linked: 0, copyCount: 0, pruned: 0, preservedManual: [], preservedModified: [], symlinkFallbacks: [], owned: normalizeOwnedMap(options.owned), ok: false, error: error.message } : 0;
251
271
  }
252
272
  }
253
273
 
@@ -309,6 +329,7 @@ export async function syncToHarness(harness, storePath, options = {}) {
309
329
  preserveManual: useOwnership,
310
330
  prune: useOwnership,
311
331
  returnDetails: useOwnership,
332
+ linkMode: options.linkMode,
312
333
  },
313
334
  );
314
335
  if (useOwnership) {
@@ -335,6 +356,7 @@ export async function syncToHarness(harness, storePath, options = {}) {
335
356
  preserveManual: useOwnership,
336
357
  prune: useOwnership,
337
358
  returnDetails: useOwnership,
359
+ linkMode: options.linkMode,
338
360
  },
339
361
  );
340
362
  if (useOwnership) {