kushi-agents 5.4.3 → 5.4.4

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 (29) hide show
  1. package/README.md +1 -0
  2. package/package.json +2 -2
  3. package/plugin/agents/kushi.agent.md +1 -0
  4. package/plugin/instructions/bootstrap-status-format.instructions.md +14 -13
  5. package/plugin/instructions/multi-user-shared-files.instructions.md +117 -87
  6. package/plugin/plugin.json +7 -4
  7. package/plugin/prompts/bootstrap.prompt.md +2 -1
  8. package/plugin/prompts/consolidate.prompt.md +4 -1
  9. package/plugin/prompts/migrate-files.prompt.md +29 -0
  10. package/plugin/skills/aggregate-project/SKILL.md +3 -3
  11. package/plugin/skills/bootstrap-project/SKILL.md +6 -6
  12. package/plugin/skills/consolidate-evidence/SKILL.md +94 -1
  13. package/plugin/skills/migrate-per-user-files/SKILL.md +47 -0
  14. package/plugin/skills/migrate-per-user-files/evals/evals.json +41 -0
  15. package/plugin/skills/migrate-per-user-files/migrate.ps1 +136 -0
  16. package/plugin/skills/migrate-per-user-files/references/migration-strategy.md +23 -0
  17. package/plugin/skills/pull-ado/SKILL.md +1 -1
  18. package/plugin/skills/pull-crm/SKILL.md +1 -1
  19. package/plugin/skills/pull-email/SKILL.md +1 -1
  20. package/plugin/skills/pull-loop/SKILL.md +1 -1
  21. package/plugin/skills/pull-meetings/SKILL.md +1 -1
  22. package/plugin/skills/pull-misc/SKILL.md +3 -3
  23. package/plugin/skills/pull-onenote/SKILL.md +1 -1
  24. package/plugin/skills/pull-sharepoint/SKILL.md +1 -1
  25. package/plugin/skills/pull-teams/SKILL.md +1 -1
  26. package/plugin/skills/refresh-project/SKILL.md +5 -5
  27. package/plugin/skills/self-check/SKILL.md +1 -0
  28. package/plugin/skills/self-check/run.ps1 +34 -0
  29. package/src/per-user-files.test.mjs +137 -0
@@ -0,0 +1,137 @@
1
+ // v5.4.4: per-user truth + auto-consolidated root view.
2
+ // Tests cover (a) bootstrap-project SKILL.md target path, (b) self-check D42
3
+ // probe (positive + negative), and (c) migrate-per-user-files migration
4
+ // script (fresh migration, idempotent no-op, defensive needs-merge).
5
+
6
+ import { test } from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+ import { spawnSync } from 'node:child_process';
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import url from 'node:url';
12
+
13
+ const here = path.dirname(url.fileURLToPath(import.meta.url));
14
+ const repoRoot = path.resolve(here, '..');
15
+ const migrate = path.join(repoRoot, 'plugin', 'skills', 'migrate-per-user-files', 'migrate.ps1');
16
+ const selfCheck = path.join(repoRoot, 'plugin', 'skills', 'self-check', 'run.ps1');
17
+
18
+ function pwsh(args) {
19
+ return spawnSync('pwsh', ['-NoProfile', '-File', ...args], { encoding: 'utf8' });
20
+ }
21
+
22
+ function tmpProject(label) {
23
+ const root = path.join(repoRoot, '.testtmp', `per-user-files-${label}-${Date.now()}`);
24
+ fs.mkdirSync(root, { recursive: true });
25
+ fs.mkdirSync(path.join(root, '.kushi', 'config', 'user'), { recursive: true });
26
+ fs.writeFileSync(
27
+ path.join(root, '.kushi', 'config', 'user', 'project-evidence.yml'),
28
+ 'alias: ushak\n'
29
+ );
30
+ return root;
31
+ }
32
+
33
+ test('bootstrap-project SKILL.md targets per-user bootstrap-status path', () => {
34
+ const skillMd = fs.readFileSync(
35
+ path.join(repoRoot, 'plugin', 'skills', 'bootstrap-project', 'SKILL.md'),
36
+ 'utf8'
37
+ );
38
+ assert.ok(
39
+ /Evidence\/<alias>\/bootstrap-status\.md/.test(skillMd),
40
+ 'bootstrap-project SKILL.md must reference Evidence/<alias>/bootstrap-status.md'
41
+ );
42
+ // No bare root mention of the three per-user files outside of the v5.4.4
43
+ // migration callout. We enforce by requiring 'Evidence/<alias>/' on every
44
+ // line that mentions bootstrap-status.md.
45
+ const lines = skillMd.split(/\r?\n/);
46
+ for (let i = 0; i < lines.length; i++) {
47
+ if (/bootstrap-status\.md/.test(lines[i])) {
48
+ const window = lines.slice(Math.max(0, i - 5), Math.min(lines.length, i + 6)).join('\n');
49
+ assert.ok(
50
+ /Evidence[\\/][^\s)`]+[\\/]|_Consolidated[\\/]/.test(window),
51
+ `bootstrap-project SKILL.md line ${i + 1} mentions bootstrap-status.md but is not per-user scoped within 5 lines`
52
+ );
53
+ }
54
+ }
55
+ });
56
+
57
+ test('self-check D42 probe catches a bare root mention in a synthetic skill', () => {
58
+ // Create a temporary skill that violates D42 and run self-check -Targeted D42 on it.
59
+ const stageRoot = path.join(repoRoot, '.testtmp', `d42-probe-${Date.now()}`);
60
+ // Mirror the minimum file shape self-check expects.
61
+ fs.mkdirSync(path.join(stageRoot, 'plugin', 'skills', 'sample-bad'), { recursive: true });
62
+ fs.mkdirSync(path.join(stageRoot, 'plugin', 'instructions'), { recursive: true });
63
+ fs.mkdirSync(path.join(stageRoot, 'plugin', 'prompts'), { recursive: true });
64
+ fs.mkdirSync(path.join(stageRoot, 'plugin', 'agents'), { recursive: true });
65
+ fs.writeFileSync(
66
+ path.join(stageRoot, 'plugin', 'skills', 'sample-bad', 'SKILL.md'),
67
+ `---\nname: "sample-bad"\nversion: "1.0.0"\ndescription: "USE WHEN testing."\n---\n\n# Sample bad\n\nThis skill writes FOLLOW-UPS.md at the project root.\n`
68
+ );
69
+ fs.writeFileSync(
70
+ path.join(stageRoot, 'plugin', 'agents', 'kushi.agent.md'),
71
+ `---\nname: "kushi"\nversion: "5.4.4"\n---\n`
72
+ );
73
+ try {
74
+ const r = pwsh([selfCheck, '-Root', stageRoot, '-Deep', '-Targeted', 'D42', '-Json']);
75
+ const findings = JSON.parse(r.stdout || '[]');
76
+ const d42 = findings.filter((f) => f.code && f.code.startsWith('D42'));
77
+ assert.ok(d42.length >= 1, `expected at least one D42 finding, got ${d42.length}`);
78
+ assert.match(d42[0].message, /FOLLOW-UPS\.md/);
79
+ } finally {
80
+ fs.rmSync(stageRoot, { recursive: true, force: true });
81
+ }
82
+ });
83
+
84
+ test('migrate-per-user-files: fresh migration moves all three root files', () => {
85
+ const root = tmpProject('fresh');
86
+ try {
87
+ fs.writeFileSync(path.join(root, 'bootstrap-status.md'), 'BS-1\n');
88
+ fs.writeFileSync(path.join(root, 'FOLLOW-UPS.md'), 'FU-1\n');
89
+ fs.writeFileSync(path.join(root, 'OPEN-QUESTIONS-DRAFT.md'), 'OQ-1\n');
90
+ const r = pwsh([migrate, '-ProjectRoot', root, '-Apply']);
91
+ assert.equal(r.status, 0, `migrate exited ${r.status}: ${r.stdout} :: ${r.stderr}`);
92
+ assert.ok(fs.existsSync(path.join(root, 'Evidence', 'ushak', 'bootstrap-status.md')));
93
+ assert.ok(fs.existsSync(path.join(root, 'Evidence', 'ushak', 'FOLLOW-UPS.md')));
94
+ assert.ok(fs.existsSync(path.join(root, 'Evidence', 'ushak', 'OPEN-QUESTIONS-DRAFT.md')));
95
+ assert.ok(!fs.existsSync(path.join(root, 'bootstrap-status.md')));
96
+ assert.ok(!fs.existsSync(path.join(root, 'FOLLOW-UPS.md')));
97
+ assert.ok(!fs.existsSync(path.join(root, 'OPEN-QUESTIONS-DRAFT.md')));
98
+ const runLog = fs.readFileSync(path.join(root, 'Evidence', 'run-log.yml'), 'utf8');
99
+ assert.match(runLog, /migrations:/);
100
+ assert.match(runLog, /alias: ushak/);
101
+ } finally {
102
+ fs.rmSync(root, { recursive: true, force: true });
103
+ }
104
+ });
105
+
106
+ test('migrate-per-user-files: re-running --apply on migrated repo is a no-op', () => {
107
+ const root = tmpProject('idem');
108
+ try {
109
+ fs.writeFileSync(path.join(root, 'bootstrap-status.md'), 'X\n');
110
+ fs.writeFileSync(path.join(root, 'FOLLOW-UPS.md'), 'Y\n');
111
+ fs.writeFileSync(path.join(root, 'OPEN-QUESTIONS-DRAFT.md'), 'Z\n');
112
+ const r1 = pwsh([migrate, '-ProjectRoot', root, '-Apply']);
113
+ assert.equal(r1.status, 0);
114
+ const r2 = pwsh([migrate, '-ProjectRoot', root, '-Apply']);
115
+ assert.equal(r2.status, 0);
116
+ assert.match(r2.stdout, /nothing-to-do/);
117
+ } finally {
118
+ fs.rmSync(root, { recursive: true, force: true });
119
+ }
120
+ });
121
+
122
+ test('migrate-per-user-files: refuses to overwrite when per-user copy differs', () => {
123
+ const root = tmpProject('merge');
124
+ try {
125
+ fs.mkdirSync(path.join(root, 'Evidence', 'ushak'), { recursive: true });
126
+ fs.writeFileSync(path.join(root, 'Evidence', 'ushak', 'bootstrap-status.md'), 'A\n');
127
+ fs.writeFileSync(path.join(root, 'bootstrap-status.md'), 'B\n');
128
+ const r = pwsh([migrate, '-ProjectRoot', root, '-Apply']);
129
+ assert.equal(r.status, 1, `expected exit 1 on needs-merge, got ${r.status}`);
130
+ assert.match(r.stdout, /needs-merge/);
131
+ // Per-user file must NOT have been overwritten.
132
+ assert.equal(fs.readFileSync(path.join(root, 'Evidence', 'ushak', 'bootstrap-status.md'), 'utf8'), 'A\n');
133
+ assert.equal(fs.readFileSync(path.join(root, 'bootstrap-status.md'), 'utf8'), 'B\n');
134
+ } finally {
135
+ fs.rmSync(root, { recursive: true, force: true });
136
+ }
137
+ });