@skillsmith/core 0.11.4 → 0.11.6

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 (240) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/README.md +10 -7
  3. package/dist/.tsbuildinfo +1 -1
  4. package/dist/src/api/client.d.ts.map +1 -1
  5. package/dist/src/api/client.js +20 -5
  6. package/dist/src/api/client.js.map +1 -1
  7. package/dist/src/api/client.private-registry.d.ts +80 -0
  8. package/dist/src/api/client.private-registry.d.ts.map +1 -0
  9. package/dist/src/api/client.private-registry.js +97 -0
  10. package/dist/src/api/client.private-registry.js.map +1 -0
  11. package/dist/src/api/client.private-registry.test.d.ts +9 -0
  12. package/dist/src/api/client.private-registry.test.d.ts.map +1 -0
  13. package/dist/src/api/client.private-registry.test.js +127 -0
  14. package/dist/src/api/client.private-registry.test.js.map +1 -0
  15. package/dist/src/api/client.test.js +39 -0
  16. package/dist/src/api/client.test.js.map +1 -1
  17. package/dist/src/api/client.toSkill.test.d.ts +25 -0
  18. package/dist/src/api/client.toSkill.test.d.ts.map +1 -0
  19. package/dist/src/api/client.toSkill.test.js +97 -0
  20. package/dist/src/api/client.toSkill.test.js.map +1 -0
  21. package/dist/src/api/client.token-refresh.d.ts +8 -0
  22. package/dist/src/api/client.token-refresh.d.ts.map +1 -1
  23. package/dist/src/api/client.token-refresh.js +26 -0
  24. package/dist/src/api/client.token-refresh.js.map +1 -1
  25. package/dist/src/api/client.token-refresh.test.js +82 -0
  26. package/dist/src/api/client.token-refresh.test.js.map +1 -1
  27. package/dist/src/api/index.d.ts +2 -0
  28. package/dist/src/api/index.d.ts.map +1 -1
  29. package/dist/src/api/index.js +9 -0
  30. package/dist/src/api/index.js.map +1 -1
  31. package/dist/src/api/security-summary.d.ts +73 -0
  32. package/dist/src/api/security-summary.d.ts.map +1 -0
  33. package/dist/src/api/security-summary.js +88 -0
  34. package/dist/src/api/security-summary.js.map +1 -0
  35. package/dist/src/api/security-summary.test.d.ts +9 -0
  36. package/dist/src/api/security-summary.test.d.ts.map +1 -0
  37. package/dist/src/api/security-summary.test.js +94 -0
  38. package/dist/src/api/security-summary.test.js.map +1 -0
  39. package/dist/src/compatibility/slugs.d.ts +7 -3
  40. package/dist/src/compatibility/slugs.d.ts.map +1 -1
  41. package/dist/src/compatibility/slugs.js +11 -4
  42. package/dist/src/compatibility/slugs.js.map +1 -1
  43. package/dist/src/config/config-atomic-write.d.ts +30 -10
  44. package/dist/src/config/config-atomic-write.d.ts.map +1 -1
  45. package/dist/src/config/config-atomic-write.js +33 -80
  46. package/dist/src/config/config-atomic-write.js.map +1 -1
  47. package/dist/src/config/config-atomic-write.test.d.ts +8 -0
  48. package/dist/src/config/config-atomic-write.test.d.ts.map +1 -1
  49. package/dist/src/config/config-atomic-write.test.js +64 -12
  50. package/dist/src/config/config-atomic-write.test.js.map +1 -1
  51. package/dist/src/config/owned-lock.acquire.d.ts +53 -0
  52. package/dist/src/config/owned-lock.acquire.d.ts.map +1 -0
  53. package/dist/src/config/owned-lock.acquire.js +140 -0
  54. package/dist/src/config/owned-lock.acquire.js.map +1 -0
  55. package/dist/src/config/owned-lock.claim.d.ts +84 -0
  56. package/dist/src/config/owned-lock.claim.d.ts.map +1 -0
  57. package/dist/src/config/owned-lock.claim.js +310 -0
  58. package/dist/src/config/owned-lock.claim.js.map +1 -0
  59. package/dist/src/config/owned-lock.d.ts +113 -0
  60. package/dist/src/config/owned-lock.d.ts.map +1 -0
  61. package/dist/src/config/owned-lock.js +121 -0
  62. package/dist/src/config/owned-lock.js.map +1 -0
  63. package/dist/src/config/owned-lock.test.d.ts +12 -0
  64. package/dist/src/config/owned-lock.test.d.ts.map +1 -0
  65. package/dist/src/config/owned-lock.test.js +412 -0
  66. package/dist/src/config/owned-lock.test.js.map +1 -0
  67. package/dist/src/config/owned-lock.types.d.ts +78 -0
  68. package/dist/src/config/owned-lock.types.d.ts.map +1 -0
  69. package/dist/src/config/owned-lock.types.js +30 -0
  70. package/dist/src/config/owned-lock.types.js.map +1 -0
  71. package/dist/src/db/createDatabase.d.ts.map +1 -1
  72. package/dist/src/db/createDatabase.js +19 -7
  73. package/dist/src/db/createDatabase.js.map +1 -1
  74. package/dist/src/db/drivers/sqljsDriver.d.ts +8 -0
  75. package/dist/src/db/drivers/sqljsDriver.d.ts.map +1 -1
  76. package/dist/src/db/drivers/sqljsDriver.js +24 -2
  77. package/dist/src/db/drivers/sqljsDriver.js.map +1 -1
  78. package/dist/src/db/migration-runner.d.ts.map +1 -1
  79. package/dist/src/db/migration-runner.js +23 -2
  80. package/dist/src/db/migration-runner.js.map +1 -1
  81. package/dist/src/db/schema.d.ts.map +1 -1
  82. package/dist/src/db/schema.js +29 -3
  83. package/dist/src/db/schema.js.map +1 -1
  84. package/dist/src/embeddings/hnsw-store.exports.d.ts +11 -0
  85. package/dist/src/embeddings/hnsw-store.exports.d.ts.map +1 -0
  86. package/dist/src/embeddings/hnsw-store.exports.js +10 -0
  87. package/dist/src/embeddings/hnsw-store.exports.js.map +1 -0
  88. package/dist/src/embeddings/index.d.ts +1 -2
  89. package/dist/src/embeddings/index.d.ts.map +1 -1
  90. package/dist/src/embeddings/index.js +17 -3
  91. package/dist/src/embeddings/index.js.map +1 -1
  92. package/dist/src/embeddings/probe.d.ts.map +1 -1
  93. package/dist/src/embeddings/probe.js +4 -8
  94. package/dist/src/embeddings/probe.js.map +1 -1
  95. package/dist/src/exports/services.d.ts +5 -1
  96. package/dist/src/exports/services.d.ts.map +1 -1
  97. package/dist/src/exports/services.js +27 -0
  98. package/dist/src/exports/services.js.map +1 -1
  99. package/dist/src/index.d.ts +3 -3
  100. package/dist/src/index.d.ts.map +1 -1
  101. package/dist/src/index.js +9 -3
  102. package/dist/src/index.js.map +1 -1
  103. package/dist/src/install/index.d.ts +2 -2
  104. package/dist/src/install/index.d.ts.map +1 -1
  105. package/dist/src/install/index.js +1 -1
  106. package/dist/src/install/index.js.map +1 -1
  107. package/dist/src/install/paths.d.ts +165 -1
  108. package/dist/src/install/paths.d.ts.map +1 -1
  109. package/dist/src/install/paths.js +254 -1
  110. package/dist/src/install/paths.js.map +1 -1
  111. package/dist/src/install/paths.test.js +174 -2
  112. package/dist/src/install/paths.test.js.map +1 -1
  113. package/dist/src/security/scanner/multiline-category-closure.test.d.ts +80 -0
  114. package/dist/src/security/scanner/multiline-category-closure.test.d.ts.map +1 -0
  115. package/dist/src/security/scanner/multiline-category-closure.test.js +287 -0
  116. package/dist/src/security/scanner/multiline-category-closure.test.js.map +1 -0
  117. package/dist/src/services/context-words.d.ts +40 -0
  118. package/dist/src/services/context-words.d.ts.map +1 -0
  119. package/dist/src/services/context-words.js +178 -0
  120. package/dist/src/services/context-words.js.map +1 -0
  121. package/dist/src/services/context-words.test.d.ts +2 -0
  122. package/dist/src/services/context-words.test.d.ts.map +1 -0
  123. package/dist/src/services/context-words.test.js +94 -0
  124. package/dist/src/services/context-words.test.js.map +1 -0
  125. package/dist/src/services/recommend-guard.d.ts +28 -0
  126. package/dist/src/services/recommend-guard.d.ts.map +1 -0
  127. package/dist/src/services/recommend-guard.js +34 -0
  128. package/dist/src/services/recommend-guard.js.map +1 -0
  129. package/dist/src/services/recommend-guard.test.d.ts +2 -0
  130. package/dist/src/services/recommend-guard.test.d.ts.map +1 -0
  131. package/dist/src/services/recommend-guard.test.js +28 -0
  132. package/dist/src/services/recommend-guard.test.js.map +1 -0
  133. package/dist/src/services/skill-installation.content.d.ts +96 -0
  134. package/dist/src/services/skill-installation.content.d.ts.map +1 -0
  135. package/dist/src/services/skill-installation.content.js +319 -0
  136. package/dist/src/services/skill-installation.content.js.map +1 -0
  137. package/dist/src/services/skill-installation.helpers.d.ts +32 -1
  138. package/dist/src/services/skill-installation.helpers.d.ts.map +1 -1
  139. package/dist/src/services/skill-installation.helpers.js +62 -7
  140. package/dist/src/services/skill-installation.helpers.js.map +1 -1
  141. package/dist/src/services/skill-installation.helpers.test.js +108 -2
  142. package/dist/src/services/skill-installation.helpers.test.js.map +1 -1
  143. package/dist/src/services/skill-installation.io.d.ts +22 -1
  144. package/dist/src/services/skill-installation.io.d.ts.map +1 -1
  145. package/dist/src/services/skill-installation.io.js +110 -6
  146. package/dist/src/services/skill-installation.io.js.map +1 -1
  147. package/dist/src/services/skill-installation.service.d.ts +22 -1
  148. package/dist/src/services/skill-installation.service.d.ts.map +1 -1
  149. package/dist/src/services/skill-installation.service.js +30 -5
  150. package/dist/src/services/skill-installation.service.js.map +1 -1
  151. package/dist/src/services/skill-installation.types.d.ts +42 -1
  152. package/dist/src/services/skill-installation.types.d.ts.map +1 -1
  153. package/dist/src/services/skill-installation.types.js.map +1 -1
  154. package/dist/src/services/skill-manifest.d.ts +21 -0
  155. package/dist/src/services/skill-manifest.d.ts.map +1 -1
  156. package/dist/src/services/skill-manifest.js +51 -6
  157. package/dist/src/services/skill-manifest.js.map +1 -1
  158. package/dist/src/services/skill-manifest.test.d.ts +2 -0
  159. package/dist/src/services/skill-manifest.test.d.ts.map +1 -0
  160. package/dist/src/services/skill-manifest.test.js +202 -0
  161. package/dist/src/services/skill-manifest.test.js.map +1 -0
  162. package/dist/src/services/skill-resolution.d.ts +67 -0
  163. package/dist/src/services/skill-resolution.d.ts.map +1 -0
  164. package/dist/src/services/skill-resolution.js +67 -0
  165. package/dist/src/services/skill-resolution.js.map +1 -0
  166. package/dist/src/services/skill-resolution.test.d.ts +2 -0
  167. package/dist/src/services/skill-resolution.test.d.ts.map +1 -0
  168. package/dist/src/services/skill-resolution.test.js +143 -0
  169. package/dist/src/services/skill-resolution.test.js.map +1 -0
  170. package/dist/src/types/skill.d.ts +10 -0
  171. package/dist/src/types/skill.d.ts.map +1 -1
  172. package/dist/src/types.d.ts +9 -5
  173. package/dist/src/types.d.ts.map +1 -1
  174. package/dist/src/utils/index.d.ts +1 -0
  175. package/dist/src/utils/index.d.ts.map +1 -1
  176. package/dist/src/utils/index.js +4 -0
  177. package/dist/src/utils/index.js.map +1 -1
  178. package/dist/src/utils/quiet-mode.d.ts +21 -0
  179. package/dist/src/utils/quiet-mode.d.ts.map +1 -0
  180. package/dist/src/utils/quiet-mode.js +26 -0
  181. package/dist/src/utils/quiet-mode.js.map +1 -0
  182. package/dist/tests/EmbeddingService.test.js +58 -1
  183. package/dist/tests/EmbeddingService.test.js.map +1 -1
  184. package/dist/tests/SecurityScanner.performance.test.js +5 -2
  185. package/dist/tests/SecurityScanner.performance.test.js.map +1 -1
  186. package/dist/tests/db/database-abstraction.test.js +45 -1
  187. package/dist/tests/db/database-abstraction.test.js.map +1 -1
  188. package/dist/tests/db/migration-runner-race.test.d.ts +2 -0
  189. package/dist/tests/db/migration-runner-race.test.d.ts.map +1 -0
  190. package/dist/tests/db/migration-runner-race.test.js +75 -0
  191. package/dist/tests/db/migration-runner-race.test.js.map +1 -0
  192. package/dist/tests/db/schema-async.test.js +37 -1
  193. package/dist/tests/db/schema-async.test.js.map +1 -1
  194. package/dist/tests/db/sqljsDriver.test.js +63 -1
  195. package/dist/tests/db/sqljsDriver.test.js.map +1 -1
  196. package/dist/tests/helpers/deterministic-dead-pid.d.ts +13 -0
  197. package/dist/tests/helpers/deterministic-dead-pid.d.ts.map +1 -0
  198. package/dist/tests/helpers/deterministic-dead-pid.js +31 -0
  199. package/dist/tests/helpers/deterministic-dead-pid.js.map +1 -0
  200. package/dist/tests/integration/owned-lock-lost-update.test.d.ts +12 -0
  201. package/dist/tests/integration/owned-lock-lost-update.test.d.ts.map +1 -0
  202. package/dist/tests/integration/owned-lock-lost-update.test.js +52 -0
  203. package/dist/tests/integration/owned-lock-lost-update.test.js.map +1 -0
  204. package/dist/tests/integration/owned-lock-reclaim-race.test.d.ts +19 -0
  205. package/dist/tests/integration/owned-lock-reclaim-race.test.d.ts.map +1 -0
  206. package/dist/tests/integration/owned-lock-reclaim-race.test.js +177 -0
  207. package/dist/tests/integration/owned-lock-reclaim-race.test.js.map +1 -0
  208. package/dist/tests/schema.test.js +43 -1
  209. package/dist/tests/schema.test.js.map +1 -1
  210. package/dist/tests/unit/services/skill-installation.content.test.d.ts +10 -0
  211. package/dist/tests/unit/services/skill-installation.content.test.d.ts.map +1 -0
  212. package/dist/tests/unit/services/skill-installation.content.test.js +290 -0
  213. package/dist/tests/unit/services/skill-installation.content.test.js.map +1 -0
  214. package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.d.ts +34 -0
  215. package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.d.ts.map +1 -0
  216. package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.js +246 -0
  217. package/dist/tests/unit/services/skill-installation.io.client-agent-path.test.js.map +1 -0
  218. package/dist/tests/unit/services/skill-installation.io.symlink.test.d.ts +8 -0
  219. package/dist/tests/unit/services/skill-installation.io.symlink.test.d.ts.map +1 -0
  220. package/dist/tests/unit/services/skill-installation.io.symlink.test.js +72 -0
  221. package/dist/tests/unit/services/skill-installation.io.symlink.test.js.map +1 -0
  222. package/dist/tests/unit/services/skill-installation.service.companion-basedir.test.d.ts +17 -0
  223. package/dist/tests/unit/services/skill-installation.service.companion-basedir.test.d.ts.map +1 -0
  224. package/dist/tests/unit/services/skill-installation.service.companion-basedir.test.js +112 -0
  225. package/dist/tests/unit/services/skill-installation.service.companion-basedir.test.js.map +1 -0
  226. package/dist/tests/unit/services/skill-installation.service.error-codes.test.d.ts +15 -0
  227. package/dist/tests/unit/services/skill-installation.service.error-codes.test.d.ts.map +1 -0
  228. package/dist/tests/unit/services/skill-installation.service.error-codes.test.js +190 -0
  229. package/dist/tests/unit/services/skill-installation.service.error-codes.test.js.map +1 -0
  230. package/dist/tests/unit/services/skill-installation.service.multi-client.test.d.ts +17 -0
  231. package/dist/tests/unit/services/skill-installation.service.multi-client.test.d.ts.map +1 -0
  232. package/dist/tests/unit/services/skill-installation.service.multi-client.test.js +126 -0
  233. package/dist/tests/unit/services/skill-installation.service.multi-client.test.js.map +1 -0
  234. package/dist/tests/unit/services/skill-installation.service.test.js +7 -112
  235. package/dist/tests/unit/services/skill-installation.service.test.js.map +1 -1
  236. package/dist/tests/utils/quiet-mode.test.d.ts +12 -0
  237. package/dist/tests/utils/quiet-mode.test.d.ts.map +1 -0
  238. package/dist/tests/utils/quiet-mode.test.js +42 -0
  239. package/dist/tests/utils/quiet-mode.test.js.map +1 -0
  240. package/package.json +17 -5
@@ -7,6 +7,8 @@
7
7
  * pythonIncremental.hardening.test.ts (SMI-4315/4316).
8
8
  */
9
9
  import { beforeEach, describe, expect, it, vi } from 'vitest';
10
+ import { homedir } from 'node:os';
11
+ import { join } from 'node:path';
10
12
  // vi.hoisted runs before vi.mock factories so we can share the spy instance.
11
13
  const { existsSyncSpy } = vi.hoisted(() => ({
12
14
  existsSyncSpy: vi.fn(() => false),
@@ -15,7 +17,7 @@ vi.mock('node:fs', async () => {
15
17
  const actual = await vi.importActual('node:fs');
16
18
  return { ...actual, existsSync: (p) => existsSyncSpy(p) };
17
19
  });
18
- import { CLIENT_IDS, CLIENT_NATIVE_PATHS, enumerateHarnessPresence } from './paths.js';
20
+ import { CANONICAL_CLIENT, CLIENT_IDS, CLIENT_NATIVE_PATHS, COMPANION_AGENT_TARGETS, enumerateHarnessPresence, getCompanionAgentTarget, resolveCompanionAgentDir, resolveCompanionAgentPath, } from './paths.js';
19
21
  describe('enumerateHarnessPresence (SMI-5390)', () => {
20
22
  beforeEach(() => {
21
23
  existsSyncSpy.mockReset();
@@ -39,6 +41,7 @@ describe('enumerateHarnessPresence (SMI-5390)', () => {
39
41
  expect(result.find((r) => r.harness === 'opencode')?.present).toBe(false);
40
42
  expect(result.find((r) => r.harness === 'hermes')?.present).toBe(false);
41
43
  expect(result.find((r) => r.harness === 'grok')?.present).toBe(false);
44
+ expect(result.find((r) => r.harness === 'antigravity')?.present).toBe(false);
42
45
  });
43
46
  it('reports all harnesses absent when existsSync returns false for every path', () => {
44
47
  existsSyncSpy.mockReturnValue(false);
@@ -76,10 +79,179 @@ describe('opencode + hermes ClientIds (SMI-5456 Wave 1 Step 5)', () => {
76
79
  describe('grok ClientId (SMI-5697)', () => {
77
80
  it('CLIENT_IDS includes grok', () => {
78
81
  expect(CLIENT_IDS).toContain('grok');
79
- expect(CLIENT_IDS).toHaveLength(8);
80
82
  });
81
83
  it('grok resolves to ~/.grok/skills', () => {
82
84
  expect(CLIENT_NATIVE_PATHS.grok.endsWith('/.grok/skills')).toBe(true);
83
85
  });
84
86
  });
87
+ describe('antigravity ClientId (SMI-5982 Wave 6)', () => {
88
+ it('CLIENT_IDS includes antigravity, 9 total', () => {
89
+ expect(CLIENT_IDS).toContain('antigravity');
90
+ expect(CLIENT_IDS).toHaveLength(9);
91
+ });
92
+ it('antigravity resolves to ~/.gemini/config/skills (corrects SMI-5179 stale ~/.gemini/antigravity/skills claim)', () => {
93
+ expect(CLIENT_NATIVE_PATHS.antigravity).toBe(join(homedir(), '.gemini', 'config', 'skills'));
94
+ });
95
+ });
96
+ /**
97
+ * SMI-5980 (Wave 3): COMPANION_AGENT_TARGETS is a regression guard by
98
+ * design — every existing ClientId's companion-subagent output path must be
99
+ * EXACTLY unchanged from the pre-Wave-3 hardcoded
100
+ * `path.join(os.homedir(), '.claude', 'agents')` default, except copilot and
101
+ * opencode, which get an independently-evidenced directory value cited from
102
+ * AGENT_SHIM_TARGETS (agent-harness-targets.ts) for the SAME underlying
103
+ * tool — not a new invented value.
104
+ */
105
+ describe('COMPANION_AGENT_TARGETS (SMI-5980 Wave 3)', () => {
106
+ it('has exactly one entry per ClientId in CLIENT_IDS', () => {
107
+ for (const id of CLIENT_IDS) {
108
+ expect(COMPANION_AGENT_TARGETS[id]).toBeDefined();
109
+ }
110
+ expect(Object.keys(COMPANION_AGENT_TARGETS)).toHaveLength(CLIENT_IDS.length);
111
+ });
112
+ it('every entry but antigravity uses flat file mode; every flat entry but copilot uses the shared {name}-specialist.md pattern', () => {
113
+ // SMI-5982 (Wave 6): antigravity is the first 'directory-package' entry —
114
+ // see the dedicated 'antigravity directory-package entry' test below.
115
+ for (const id of CLIENT_IDS) {
116
+ if (id === 'antigravity')
117
+ continue;
118
+ expect(COMPANION_AGENT_TARGETS[id].fileMode).toBe('flat');
119
+ if (id === 'copilot')
120
+ continue;
121
+ expect(COMPANION_AGENT_TARGETS[id].filenamePattern).toBe('{name}-specialist.md');
122
+ }
123
+ });
124
+ it('copilot uses {name}.agent.md — its own independently-evidenced filename, not the shared -specialist.md suffix (PR-review finding, BLOCKING)', () => {
125
+ // AGENT_SHIM_TARGETS.copilot (agent-harness-targets.ts) and shims.ts's
126
+ // own doc comment both independently confirm Copilot's real
127
+ // companion-agent format is `.agent.md` -- a plain `-specialist.md`
128
+ // suffix risked producing a file Copilot's own surfaces never discover.
129
+ expect(COMPANION_AGENT_TARGETS.copilot.filenamePattern).toBe('{name}.agent.md');
130
+ });
131
+ it('claude-code matches the pre-Wave-3 hardcoded default exactly', () => {
132
+ // Pre-Wave-3: path.join(os.homedir(), '.claude', 'agents') — the exact
133
+ // literal skill-installation.io.ts and author/utils.ts both hardcoded.
134
+ expect(COMPANION_AGENT_TARGETS['claude-code'].dir).toBe(join(homedir(), '.claude', 'agents'));
135
+ });
136
+ it.each(['cursor', 'windsurf', 'agents', 'hermes', 'grok'])('%s has no independent AGENT_SHIM_TARGETS evidence — defaults to the claude-code value', (client) => {
137
+ expect(COMPANION_AGENT_TARGETS[client].dir).toBe(COMPANION_AGENT_TARGETS['claude-code'].dir);
138
+ });
139
+ it('copilot uses its own independently-evidenced directory, not the claude-code default', () => {
140
+ expect(COMPANION_AGENT_TARGETS.copilot.dir.endsWith('/.copilot/agents')).toBe(true);
141
+ expect(COMPANION_AGENT_TARGETS.copilot.dir).not.toBe(COMPANION_AGENT_TARGETS['claude-code'].dir);
142
+ });
143
+ it('opencode uses its own independently-evidenced directory, not the claude-code default', () => {
144
+ expect(COMPANION_AGENT_TARGETS.opencode.dir.endsWith('/.config/opencode/agents')).toBe(true);
145
+ expect(COMPANION_AGENT_TARGETS.opencode.dir).not.toBe(COMPANION_AGENT_TARGETS['claude-code'].dir);
146
+ });
147
+ it('antigravity directory-package entry: its own dir, directory-package mode, fixed agent.md filename (SMI-5982 Wave 6)', () => {
148
+ // Project-scoped relative dir (Step 1 of the SMI-5982 plan: no existing
149
+ // global-vs-project distinction anywhere in this CLI to hook into) — NOT
150
+ // homedir()-anchored like every other client's dir.
151
+ expect(COMPANION_AGENT_TARGETS.antigravity.dir).toBe(join('.agents', 'agents'));
152
+ expect(COMPANION_AGENT_TARGETS.antigravity.dir).not.toBe(COMPANION_AGENT_TARGETS['claude-code'].dir);
153
+ expect(COMPANION_AGENT_TARGETS.antigravity.fileMode).toBe('directory-package');
154
+ // No {name} token — the skill name lives in the directory segment, not the filename.
155
+ expect(COMPANION_AGENT_TARGETS.antigravity.filenamePattern).toBe('agent.md');
156
+ });
157
+ });
158
+ describe('getCompanionAgentTarget / resolveCompanionAgentDir / resolveCompanionAgentPath (SMI-5980 Wave 3)', () => {
159
+ it('getCompanionAgentTarget defaults to CANONICAL_CLIENT when no client is passed', () => {
160
+ expect(getCompanionAgentTarget()).toEqual(COMPANION_AGENT_TARGETS[CANONICAL_CLIENT]);
161
+ });
162
+ it('getCompanionAgentTarget returns the exact per-client entry', () => {
163
+ for (const id of CLIENT_IDS) {
164
+ expect(getCompanionAgentTarget(id)).toEqual(COMPANION_AGENT_TARGETS[id]);
165
+ }
166
+ });
167
+ it('resolveCompanionAgentDir defaults to the canonical (claude-code) dir', () => {
168
+ expect(resolveCompanionAgentDir()).toBe(COMPANION_AGENT_TARGETS['claude-code'].dir);
169
+ });
170
+ it.each(CLIENT_IDS.filter((id) => id !== 'copilot' && id !== 'antigravity'))('resolveCompanionAgentPath(%s) matches dir + <skillName>-specialist.md for every flat client but copilot', (client) => {
171
+ const result = resolveCompanionAgentPath('my-skill', client);
172
+ expect(result).toBe(join(COMPANION_AGENT_TARGETS[client].dir, 'my-skill-specialist.md'));
173
+ });
174
+ it('resolveCompanionAgentPath(copilot) matches dir + <skillName>.agent.md', () => {
175
+ const result = resolveCompanionAgentPath('my-skill', 'copilot');
176
+ expect(result).toBe(join(COMPANION_AGENT_TARGETS.copilot.dir, 'my-skill.agent.md'));
177
+ });
178
+ it('resolveCompanionAgentPath(antigravity) matches dir/<skillName>/agent.md — directory-package mode (SMI-5982 Wave 6)', () => {
179
+ // SMI-5982 code-review fix #1: antigravity's `dir` is RELATIVE, so the
180
+ // 3rd `baseDir` param is resolved against it explicitly, producing an
181
+ // ABSOLUTE path. PR-review follow-up: `baseDir` is now REQUIRED for
182
+ // directory-package mode (no `process.cwd()` default) — see the
183
+ // dedicated 'resolveCompanionAgentPath baseDir resolution' describe
184
+ // block below for the required-baseDir + traversal-guard coverage.
185
+ const result = resolveCompanionAgentPath('my-skill', 'antigravity', process.cwd());
186
+ expect(result).toBe(join(process.cwd(), COMPANION_AGENT_TARGETS.antigravity.dir, 'my-skill', 'agent.md'));
187
+ expect(result).toBe(join(process.cwd(), '.agents', 'agents', 'my-skill', 'agent.md'));
188
+ });
189
+ it('resolveCompanionAgentPath defaults to canonical client when omitted (regression: unchanged)', () => {
190
+ const result = resolveCompanionAgentPath('my-skill');
191
+ expect(result).toBe(join(COMPANION_AGENT_TARGETS['claude-code'].dir, 'my-skill-specialist.md'));
192
+ });
193
+ });
194
+ /**
195
+ * SMI-5982 code-review fix #1 (BLOCKING, cwd-dependent resolution): the
196
+ * `baseDir` param makes the resolution root explicit instead of an implicit
197
+ * `process.cwd()` lookup at whatever moment an `fs` call eventually consumes
198
+ * the returned path — critical for the long-running MCP server, whose cwd is
199
+ * fixed at launch and generally does not track the calling editor/agent's
200
+ * actual project.
201
+ *
202
+ * PR-review follow-up (BLOCKING): the original `baseDir: string =
203
+ * process.cwd()` default only closed the bug for callers whose own cwd
204
+ * happens to be the caller's real project — it silently did nothing for
205
+ * every OTHER production call site (e.g. the MCP private-registry install
206
+ * action, which has no per-call cwd input at all). `baseDir` is now
207
+ * **required** (no default) for every `directory-package`-mode client —
208
+ * omitting it throws instead of silently resolving against this process's
209
+ * ambient cwd, closing the bug class by construction for every current and
210
+ * future caller.
211
+ */
212
+ describe('resolveCompanionAgentPath baseDir resolution (SMI-5982 code-review fix #1)', () => {
213
+ it('resolves a directory-package (antigravity) relative dir against an explicit baseDir', () => {
214
+ const result = resolveCompanionAgentPath('my-skill', 'antigravity', '/some/explicit/root');
215
+ expect(result).toBe('/some/explicit/root/.agents/agents/my-skill/agent.md');
216
+ });
217
+ it('omitting baseDir for a directory-package client (antigravity) now throws instead of defaulting to process.cwd() (PR-review follow-up)', () => {
218
+ expect(() => resolveCompanionAgentPath('my-skill', 'antigravity')).toThrow(/directory-package mode.*explicit baseDir is required/s);
219
+ });
220
+ it('explicit baseDir still succeeds for antigravity (regression guard)', () => {
221
+ expect(() => resolveCompanionAgentPath('my-skill', 'antigravity', process.cwd())).not.toThrow();
222
+ const result = resolveCompanionAgentPath('my-skill', 'antigravity', process.cwd());
223
+ expect(result).toBe(join(process.cwd(), '.agents', 'agents', 'my-skill', 'agent.md'));
224
+ });
225
+ it('flat-mode clients (e.g. claude-code) are unaffected by the required-baseDir guard — omitting baseDir still resolves correctly (regression guard)', () => {
226
+ const withBaseDir = resolveCompanionAgentPath('my-skill', 'claude-code', '/some/explicit/root');
227
+ const withoutBaseDir = resolveCompanionAgentPath('my-skill', 'claude-code');
228
+ expect(withBaseDir).toBe(withoutBaseDir);
229
+ expect(withBaseDir).toBe(join(COMPANION_AGENT_TARGETS['claude-code'].dir, 'my-skill-specialist.md'));
230
+ });
231
+ });
232
+ /**
233
+ * SMI-5982 code-review fix #2 (BLOCKING, path traversal): `skillName` becomes
234
+ * its own path segment in directory-package mode, so an unsanitized value
235
+ * like '..' would escape the intended companion-agent namespace. Every
236
+ * current caller already sanitizes skillName upstream, but this function is
237
+ * exported and reusable, so it validates independently (same "last line of
238
+ * defense" principle as `skillNameFromSkillId()` in
239
+ * skill-installation.content.ts).
240
+ */
241
+ describe('resolveCompanionAgentPath directory-package skillName validation (SMI-5982 code-review fix #2)', () => {
242
+ // baseDir is passed explicitly throughout this block so these cases exercise the skillName
243
+ // validation specifically, not the required-baseDir guard (fix #1, PR-review follow-up) — that
244
+ // guard runs first and would otherwise mask the skillName check being tested here.
245
+ it.each(['..', '.', '', 'foo/bar', 'foo\\bar'])('throws for unsafe skillName %j in directory-package mode (antigravity)', (unsafeName) => {
246
+ expect(() => resolveCompanionAgentPath(unsafeName, 'antigravity', process.cwd())).toThrow(/Unsafe skill name for directory-package companion path/);
247
+ });
248
+ it('still succeeds for a normal skillName (regression guard)', () => {
249
+ expect(() => resolveCompanionAgentPath('my-skill', 'antigravity', process.cwd())).not.toThrow();
250
+ });
251
+ it.each(['..', '.', '', 'foo/bar', 'foo\\bar'])('flat-mode clients are unaffected by the directory-package validation — %j still resolves for claude-code', (name) => {
252
+ expect(() => resolveCompanionAgentPath(name, 'claude-code')).not.toThrow();
253
+ const result = resolveCompanionAgentPath(name, 'claude-code');
254
+ expect(result).toBe(join(COMPANION_AGENT_TARGETS['claude-code'].dir, `${name}-specialist.md`));
255
+ });
256
+ });
85
257
  //# sourceMappingURL=paths.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"paths.test.js","sourceRoot":"","sources":["../../../src/install/paths.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"paths.test.js","sourceRoot":"","sources":["../../../src/install/paths.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * SMI-5879 Wave 3 item 2 — structural closure test for cohort E's exclusion
3
+ * proof (design doc `smi-5879-edge-twin-parity-design.md` §8.3.1.2.2).
4
+ * @module @skillsmith/core/security/scanner/multiline-category-closure.test
5
+ *
6
+ * WHY THIS TEST EXISTS
7
+ *
8
+ * The SMI-5879 census (comparing pre-port vs post-port `skills.security_score`
9
+ * across ~344k rows) needs to fully re-simulate every row scoring `>= 8`
10
+ * (cohort C1) but can structurally EXCLUDE every row scoring `0-7` (cohort E)
11
+ * from that expensive re-simulation — *if and only if* three properties about
12
+ * how the multiline pass routes patterns hold. The design doc's §8.3.1.2.1
13
+ * proves those properties in prose ("P1/P2/P3"); this file promotes that proof
14
+ * to a machine-checked gate (design doc gate G-5) so a future source change
15
+ * that silently invalidates the premise fails CI instead of silently
16
+ * corrupting the census's soundness.
17
+ *
18
+ * THE +32 ARITHMETIC THIS TEST PROTECTS (not itself re-derived here — that is
19
+ * `weights.ts` + the design doc's §8.3.1.2 arithmetic; this file only proves
20
+ * the ROUTING facts the arithmetic depends on):
21
+ * max joint contribution = 100 * CATEGORY_WEIGHTS.jailbreak.coefficient (0.20)
22
+ * + 100 * CATEGORY_WEIGHTS.ai_defence.coefficient (0.12)
23
+ * = 32.0
24
+ * S <= 7 => S' <= S + 32 <= 39 < 40 (QUARANTINE_THRESHOLD) => verdict
25
+ * cannot flip to quarantined for any row starting at or below 7.
26
+ * That bound is sound ONLY IF the multiline pass can never inflate any
27
+ * category's subtotal OTHER than jailbreak / ai_defence. The three assertions
28
+ * below are exactly what closes that "only if".
29
+ *
30
+ * THE DOCUMENTED TRAP (design doc §8.3.1.2.3) — DELIBERATELY NOT REPEATED HERE
31
+ *
32
+ * The design doc warns against writing assertion 3 as "every pattern for
33
+ * which `isMultilinePattern()` returns true belongs to an AI-category array"
34
+ * — that predicate is syntactic (it fires on a NEGATED class `[^\n]`, the
35
+ * literal opposite of "spans lines") and is false today for 9 patterns across
36
+ * two non-AI arrays. This file does not use that predicate at all: assertions
37
+ * 1 and 3 are asserted over the multiline pass's CALL SITES (which pattern
38
+ * array is physically wired to `scanPatternsWithMultilineSupport`'s `patterns:`
39
+ * config field), not over any per-pattern syntactic test. That is the
40
+ * "routing, not predicate" distinction the trap section requires.
41
+ *
42
+ * SMI-5881 ADAPTATION (judgment call — flagged, not silently resolved)
43
+ *
44
+ * The design doc's assertion 2 is written directly against `isMultilinePattern()`
45
+ * as the definition of "PASS-1 pattern". SMI-5881 (merged before this file was
46
+ * written) DELETED `isMultilinePattern()` entirely — no compatibility shim —
47
+ * and replaced the syntactic heuristic with `PATTERN_SCOPE` /
48
+ * `resolvePatternScope()` (`./patterns.scope.ts`), an explicit per-pattern
49
+ * `'line' | 'content' | 'both'` declaration. `isMultilinePattern` cannot be
50
+ * imported here; it no longer exists anywhere under `packages/core/src`
51
+ * (guarded by `packages/core/tests/security/pattern-scope.test.ts`).
52
+ *
53
+ * `scanPatternsWithMultilineSupport`'s pass 1 (SecurityScanner.helpers.ts) now
54
+ * reads: `if (resolvePatternScope(pattern) === 'line') continue` — i.e. pass 1
55
+ * tests every pattern whose resolved scope is NOT `'line'` (`'content'` or
56
+ * `'both'`). That predicate is the faithful, current-source equivalent of the
57
+ * design doc's `routed.filter(isMultilinePattern)` — same set membership
58
+ * question ("which patterns does pass 1 actually read"), expressed against
59
+ * the model that replaced the deleted one. Assertion 2 below uses
60
+ * `resolvePatternScope(p) !== 'line'` for exactly this reason.
61
+ *
62
+ * CODE-REVIEW REMEDIATION (Finding 1 — NO-GO, fixed)
63
+ *
64
+ * A first version of this file enumerated call sites with a bounded
65
+ * text-window regex scan (`CALL_TOKEN = 'scanPatternsWithMultilineSupport('`
66
+ * + an `EXTRACTION_WINDOW`-char lookahead for `type:`/`patterns:`). Codex
67
+ * (gpt-5.6-sol) review flagged that as a silent-false-negative hazard: a new
68
+ * call site using extra whitespace before `(`, a locally-aliased identifier,
69
+ * or a renamed import would be invisible to that regex while the existing two
70
+ * call sites kept the count green — defeating the point of a closure proof
71
+ * meant to catch exactly this class of drift. `extractMultilineCallSites`
72
+ * below instead walks the real TypeScript AST (`ts.createSourceFile` +
73
+ * `CallExpression` traversal resolved through `collectAliases`), which is
74
+ * exhaustive by construction against all three variants. Verified via mutation
75
+ * testing: a temporarily-added call through a whitespace-before-paren AND a
76
+ * locally-aliased form was both correctly detected (count became 3, assertion
77
+ * 1 failed) before being reverted.
78
+ */
79
+ export {};
80
+ //# sourceMappingURL=multiline-category-closure.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiline-category-closure.test.d.ts","sourceRoot":"","sources":["../../../../src/security/scanner/multiline-category-closure.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG"}
@@ -0,0 +1,287 @@
1
+ /**
2
+ * SMI-5879 Wave 3 item 2 — structural closure test for cohort E's exclusion
3
+ * proof (design doc `smi-5879-edge-twin-parity-design.md` §8.3.1.2.2).
4
+ * @module @skillsmith/core/security/scanner/multiline-category-closure.test
5
+ *
6
+ * WHY THIS TEST EXISTS
7
+ *
8
+ * The SMI-5879 census (comparing pre-port vs post-port `skills.security_score`
9
+ * across ~344k rows) needs to fully re-simulate every row scoring `>= 8`
10
+ * (cohort C1) but can structurally EXCLUDE every row scoring `0-7` (cohort E)
11
+ * from that expensive re-simulation — *if and only if* three properties about
12
+ * how the multiline pass routes patterns hold. The design doc's §8.3.1.2.1
13
+ * proves those properties in prose ("P1/P2/P3"); this file promotes that proof
14
+ * to a machine-checked gate (design doc gate G-5) so a future source change
15
+ * that silently invalidates the premise fails CI instead of silently
16
+ * corrupting the census's soundness.
17
+ *
18
+ * THE +32 ARITHMETIC THIS TEST PROTECTS (not itself re-derived here — that is
19
+ * `weights.ts` + the design doc's §8.3.1.2 arithmetic; this file only proves
20
+ * the ROUTING facts the arithmetic depends on):
21
+ * max joint contribution = 100 * CATEGORY_WEIGHTS.jailbreak.coefficient (0.20)
22
+ * + 100 * CATEGORY_WEIGHTS.ai_defence.coefficient (0.12)
23
+ * = 32.0
24
+ * S <= 7 => S' <= S + 32 <= 39 < 40 (QUARANTINE_THRESHOLD) => verdict
25
+ * cannot flip to quarantined for any row starting at or below 7.
26
+ * That bound is sound ONLY IF the multiline pass can never inflate any
27
+ * category's subtotal OTHER than jailbreak / ai_defence. The three assertions
28
+ * below are exactly what closes that "only if".
29
+ *
30
+ * THE DOCUMENTED TRAP (design doc §8.3.1.2.3) — DELIBERATELY NOT REPEATED HERE
31
+ *
32
+ * The design doc warns against writing assertion 3 as "every pattern for
33
+ * which `isMultilinePattern()` returns true belongs to an AI-category array"
34
+ * — that predicate is syntactic (it fires on a NEGATED class `[^\n]`, the
35
+ * literal opposite of "spans lines") and is false today for 9 patterns across
36
+ * two non-AI arrays. This file does not use that predicate at all: assertions
37
+ * 1 and 3 are asserted over the multiline pass's CALL SITES (which pattern
38
+ * array is physically wired to `scanPatternsWithMultilineSupport`'s `patterns:`
39
+ * config field), not over any per-pattern syntactic test. That is the
40
+ * "routing, not predicate" distinction the trap section requires.
41
+ *
42
+ * SMI-5881 ADAPTATION (judgment call — flagged, not silently resolved)
43
+ *
44
+ * The design doc's assertion 2 is written directly against `isMultilinePattern()`
45
+ * as the definition of "PASS-1 pattern". SMI-5881 (merged before this file was
46
+ * written) DELETED `isMultilinePattern()` entirely — no compatibility shim —
47
+ * and replaced the syntactic heuristic with `PATTERN_SCOPE` /
48
+ * `resolvePatternScope()` (`./patterns.scope.ts`), an explicit per-pattern
49
+ * `'line' | 'content' | 'both'` declaration. `isMultilinePattern` cannot be
50
+ * imported here; it no longer exists anywhere under `packages/core/src`
51
+ * (guarded by `packages/core/tests/security/pattern-scope.test.ts`).
52
+ *
53
+ * `scanPatternsWithMultilineSupport`'s pass 1 (SecurityScanner.helpers.ts) now
54
+ * reads: `if (resolvePatternScope(pattern) === 'line') continue` — i.e. pass 1
55
+ * tests every pattern whose resolved scope is NOT `'line'` (`'content'` or
56
+ * `'both'`). That predicate is the faithful, current-source equivalent of the
57
+ * design doc's `routed.filter(isMultilinePattern)` — same set membership
58
+ * question ("which patterns does pass 1 actually read"), expressed against
59
+ * the model that replaced the deleted one. Assertion 2 below uses
60
+ * `resolvePatternScope(p) !== 'line'` for exactly this reason.
61
+ *
62
+ * CODE-REVIEW REMEDIATION (Finding 1 — NO-GO, fixed)
63
+ *
64
+ * A first version of this file enumerated call sites with a bounded
65
+ * text-window regex scan (`CALL_TOKEN = 'scanPatternsWithMultilineSupport('`
66
+ * + an `EXTRACTION_WINDOW`-char lookahead for `type:`/`patterns:`). Codex
67
+ * (gpt-5.6-sol) review flagged that as a silent-false-negative hazard: a new
68
+ * call site using extra whitespace before `(`, a locally-aliased identifier,
69
+ * or a renamed import would be invisible to that regex while the existing two
70
+ * call sites kept the count green — defeating the point of a closure proof
71
+ * meant to catch exactly this class of drift. `extractMultilineCallSites`
72
+ * below instead walks the real TypeScript AST (`ts.createSourceFile` +
73
+ * `CallExpression` traversal resolved through `collectAliases`), which is
74
+ * exhaustive by construction against all three variants. Verified via mutation
75
+ * testing: a temporarily-added call through a whitespace-before-paren AND a
76
+ * locally-aliased form was both correctly detected (count became 3, assertion
77
+ * 1 failed) before being reverted.
78
+ */
79
+ import { readFileSync } from 'node:fs';
80
+ import { join } from 'node:path';
81
+ import { fileURLToPath } from 'node:url';
82
+ import { describe, it, expect } from 'vitest';
83
+ import * as ts from 'typescript';
84
+ import { JAILBREAK_PATTERNS, AI_DEFENCE_PATTERNS } from './patterns.jailbreak.js';
85
+ import { EVIDENCE_TYPE_BY_PATTERN } from './patterns.jailbreak.evidence.js';
86
+ import { resolvePatternScope } from './patterns.scope.js';
87
+ const __dirname = fileURLToPath(new URL('.', import.meta.url));
88
+ const SCANNER_PATH = join(__dirname, 'SecurityScanner.ts');
89
+ const SCANNER_SRC = readFileSync(SCANNER_PATH, 'utf-8');
90
+ /** The only two pattern arrays a `scanPatternsWithMultilineSupport` call site may name. */
91
+ const AI_CATEGORY_ARRAY_NAMES = ['JAILBREAK_PATTERNS', 'AI_DEFENCE_PATTERNS'];
92
+ /**
93
+ * Every OTHER pattern array in the scanner (design doc §8.3.1.2.2 assertion 3's
94
+ * explicit exclusion list). `BLOCKED_PATTERNS` is not a real exported const
95
+ * (blocked patterns are an instance-level `ScannerOptions.blockedPatterns`,
96
+ * `SecurityScanner.ts:73`) — kept in the list anyway because the design doc
97
+ * names it explicitly and the check is "this identifier never appears as a
98
+ * `patterns:` value", which is true (and meaningfully checked) regardless of
99
+ * whether the identifier is otherwise reachable.
100
+ */
101
+ const NON_AI_ARRAY_NAMES = [
102
+ 'SUSPICIOUS_PATTERNS',
103
+ 'DATA_EXFILTRATION_PATTERNS',
104
+ 'PRIVILEGE_ESCALATION_PATTERNS',
105
+ 'CODE_EXECUTION_PATTERNS',
106
+ 'BLOCKED_PATTERNS',
107
+ ];
108
+ const TARGET_FUNCTION_NAME = 'scanPatternsWithMultilineSupport';
109
+ /** The textual callee name of a call expression: `foo(` -> "foo"; `x.bar(` -> "bar". */
110
+ function calleeName(expr) {
111
+ if (ts.isIdentifier(expr))
112
+ return expr.text;
113
+ if (ts.isPropertyAccessExpression(expr))
114
+ return expr.name.text;
115
+ return undefined;
116
+ }
117
+ /**
118
+ * Every local identifier in `sourceFile` that resolves to `targetName` — the
119
+ * name itself, any `import { targetName as Local }` rename, and any simple
120
+ * `const Local = <alias>` variable alias (resolved transitively, to a fixed
121
+ * point, so a chain like `const a = target; const b = a` also resolves).
122
+ * This is what makes the call-site walk below exhaustive against the "an
123
+ * alias" failure mode the code review named: a call reached through a
124
+ * renamed import or a locally-aliased identifier still resolves back to
125
+ * `targetName` and is detected, where a literal-token text scan would miss it.
126
+ */
127
+ function collectAliases(sourceFile, targetName) {
128
+ const aliases = new Set([targetName]);
129
+ for (const stmt of sourceFile.statements) {
130
+ if (!ts.isImportDeclaration(stmt) || !stmt.importClause?.namedBindings)
131
+ continue;
132
+ const bindings = stmt.importClause.namedBindings;
133
+ if (!ts.isNamedImports(bindings))
134
+ continue;
135
+ for (const el of bindings.elements) {
136
+ const imported = el.propertyName?.text ?? el.name.text;
137
+ if (imported === targetName)
138
+ aliases.add(el.name.text);
139
+ }
140
+ }
141
+ let changed = true;
142
+ while (changed) {
143
+ changed = false;
144
+ const visit = (node) => {
145
+ if (ts.isVariableDeclaration(node) &&
146
+ ts.isIdentifier(node.name) &&
147
+ node.initializer &&
148
+ ts.isIdentifier(node.initializer) &&
149
+ aliases.has(node.initializer.text) &&
150
+ !aliases.has(node.name.text)) {
151
+ aliases.add(node.name.text);
152
+ changed = true;
153
+ }
154
+ ts.forEachChild(node, visit);
155
+ };
156
+ ts.forEachChild(sourceFile, visit);
157
+ }
158
+ return aliases;
159
+ }
160
+ /** The textual name of an object-literal member (identifier or string-literal key). */
161
+ function memberName(prop) {
162
+ if (!prop.name)
163
+ return undefined;
164
+ if (ts.isIdentifier(prop.name))
165
+ return prop.name.text;
166
+ if (ts.isStringLiteral(prop.name))
167
+ return prop.name.text;
168
+ return undefined;
169
+ }
170
+ /**
171
+ * Extract every `scanPatternsWithMultilineSupport(...)` call site in `source`
172
+ * (at path `filePath`, used only for error messages), together with its
173
+ * config object's `type:` and `patterns:` values — via full TypeScript AST
174
+ * traversal (`ts.createSourceFile` + a `CallExpression` walk resolved through
175
+ * `collectAliases`), NOT text/regex matching over a bounded window. This is
176
+ * exhaustive by construction: extra whitespace before `(`, a call reached
177
+ * through a renamed import or a locally-aliased identifier, and a config
178
+ * object appearing in any argument position are all still found — closing
179
+ * the class of silent false-negative a bounded text-window regex scan cannot
180
+ * rule out (the defect the code review's Finding 1 identified).
181
+ *
182
+ * Throws (rather than silently skipping) when a matched call's arguments
183
+ * don't have the expected `{ type: '<string literal>', patterns: <bare
184
+ * identifier> }` shape — an unparseable/unexpected call site must fail
185
+ * loudly, not be dropped from the count assertion 1 depends on, exactly as
186
+ * the prior text-based version did for its own class of unparseable input.
187
+ */
188
+ function extractMultilineCallSites(filePath, source) {
189
+ const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
190
+ const aliases = collectAliases(sourceFile, TARGET_FUNCTION_NAME);
191
+ const sites = [];
192
+ const visit = (node) => {
193
+ if (ts.isCallExpression(node)) {
194
+ const name = calleeName(node.expression);
195
+ if (name && aliases.has(name)) {
196
+ sites.push(extractCallSiteConfig(node, sourceFile, filePath));
197
+ }
198
+ }
199
+ ts.forEachChild(node, visit);
200
+ };
201
+ visit(sourceFile);
202
+ return sites;
203
+ }
204
+ /** Extract and validate the `{ type, patterns }` config object of a matched call. */
205
+ function extractCallSiteConfig(call, sourceFile, filePath) {
206
+ const offset = call.getStart(sourceFile);
207
+ const configArg = call.arguments.find((arg) => ts.isObjectLiteralExpression(arg) &&
208
+ arg.properties.some((p) => memberName(p) === 'type') &&
209
+ arg.properties.some((p) => memberName(p) === 'patterns'));
210
+ if (!configArg) {
211
+ throw new Error(`[multiline-category-closure] ${TARGET_FUNCTION_NAME} call at ${filePath}:${offset} has no ` +
212
+ `object-literal argument with both 'type' and 'patterns' properties. Either the call shape ` +
213
+ `changed (update this extraction) or this is a genuine new/malformed call site — both ` +
214
+ `invalidate the closure proof and must be resolved before this test can pass.`);
215
+ }
216
+ const typeProp = configArg.properties.find((p) => memberName(p) === 'type');
217
+ const patternsProp = configArg.properties.find((p) => memberName(p) === 'patterns');
218
+ if (!typeProp ||
219
+ !ts.isPropertyAssignment(typeProp) ||
220
+ !ts.isStringLiteral(typeProp.initializer)) {
221
+ throw new Error(`[multiline-category-closure] ${TARGET_FUNCTION_NAME} call at ${filePath}:${offset} has a ` +
222
+ `'type' property that is not a plain string literal — this is exactly the "expression ` +
223
+ `argument" shape this AST census exists to catch. Resolve explicitly before this test can pass.`);
224
+ }
225
+ if (!patternsProp ||
226
+ !ts.isPropertyAssignment(patternsProp) ||
227
+ !ts.isIdentifier(patternsProp.initializer)) {
228
+ throw new Error(`[multiline-category-closure] ${TARGET_FUNCTION_NAME} call at ${filePath}:${offset} has a ` +
229
+ `'patterns' property that is not a bare identifier — either a property-access / expression ` +
230
+ `argument was introduced (the exact "silent miss" class this test exists to catch) or the ` +
231
+ `call shape genuinely changed. Resolve explicitly before this test can pass.`);
232
+ }
233
+ return {
234
+ offset,
235
+ type: typeProp.initializer.text,
236
+ patternsIdent: patternsProp.initializer.text,
237
+ };
238
+ }
239
+ describe('SMI-5879 Wave 3 item 2 — multiline-pass category closure (design doc §8.3.1.2.2)', () => {
240
+ const callSites = extractMultilineCallSites(SCANNER_PATH, SCANNER_SRC);
241
+ // --------------------------------------------------------------------
242
+ // Assertion 1 — the multiline pass has exactly two call sites, both AI-category
243
+ // --------------------------------------------------------------------
244
+ describe('assertion 1 — exactly two call sites, both AI-category (design doc P1/P2)', () => {
245
+ it('scanPatternsWithMultilineSupport is called exactly twice in SecurityScanner.ts', () => {
246
+ expect(callSites).toHaveLength(2);
247
+ });
248
+ it('the extracted type: multiset equals exactly [ai_defence, jailbreak] — no more, no fewer, no substitution', () => {
249
+ const types = callSites.map((s) => s.type).sort();
250
+ expect(types).toEqual(['ai_defence', 'jailbreak']);
251
+ });
252
+ });
253
+ // --------------------------------------------------------------------
254
+ // Assertion 2 — every PASS-1 pattern is explicitly classified
255
+ // --------------------------------------------------------------------
256
+ describe('assertion 2 — every pass-1 pattern is explicitly classified (SMI-5881-adapted)', () => {
257
+ // "Pass 1" = every pattern scanPatternsWithMultilineSupport's first pass
258
+ // actually reads: resolvePatternScope(pattern) !== 'line' (see the
259
+ // SMI-5881 ADAPTATION module-doc comment above for why this replaces the
260
+ // design doc's isMultilinePattern-filter wording).
261
+ const routed = [...JAILBREAK_PATTERNS, ...AI_DEFENCE_PATTERNS];
262
+ const pass1 = routed.filter((p) => resolvePatternScope(p) !== 'line');
263
+ it('the pass-1 subset is non-empty — the pass is not vacuous', () => {
264
+ expect(pass1.length).toBeGreaterThan(0);
265
+ });
266
+ it('every pass-1 pattern has an explicit EVIDENCE_TYPE_BY_PATTERN entry (object identity, not fail-closed default)', () => {
267
+ for (const p of pass1) {
268
+ expect(EVIDENCE_TYPE_BY_PATTERN.has(p), `pattern /${p.source}/${p.flags} has no explicit evidence-tier entry`).toBe(true);
269
+ }
270
+ });
271
+ });
272
+ // --------------------------------------------------------------------
273
+ // Assertion 3 — no non-AI pattern array is routed through the multiline pass
274
+ // --------------------------------------------------------------------
275
+ describe('assertion 3 — no non-AI pattern array is routed through the multiline pass (design doc P3, NOT the isMultilinePattern predicate)', () => {
276
+ it('every call site patterns: identifier is one of the two AI-category arrays', () => {
277
+ for (const site of callSites) {
278
+ expect(AI_CATEGORY_ARRAY_NAMES, `call site at offset ${site.offset} routes ${site.patternsIdent}, not an AI-category array`).toContain(site.patternsIdent);
279
+ }
280
+ });
281
+ it.each(NON_AI_ARRAY_NAMES)('%s never appears as a patterns: identifier at any call site', (arrayName) => {
282
+ const idents = callSites.map((s) => s.patternsIdent);
283
+ expect(idents).not.toContain(arrayName);
284
+ });
285
+ });
286
+ });
287
+ //# sourceMappingURL=multiline-category-closure.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"multiline-category-closure.test.js","sourceRoot":"","sources":["../../../../src/security/scanner/multiline-category-closure.test.ts"],"names":[],"mappings":"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"}