akm-cli 0.7.5 → 0.8.0-rc.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 (236) hide show
  1. package/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
  2. package/README.md +20 -4
  3. package/SECURITY.md +93 -0
  4. package/dist/cli/config-migrate.js +144 -0
  5. package/dist/cli/config-validate.js +39 -0
  6. package/dist/cli/confirm.js +73 -0
  7. package/dist/cli/parse-args.js +133 -0
  8. package/dist/cli.js +1995 -551
  9. package/dist/commands/agent-dispatch.js +110 -0
  10. package/dist/commands/agent-support.js +68 -0
  11. package/dist/commands/completions.js +3 -0
  12. package/dist/commands/config-cli.js +130 -534
  13. package/dist/commands/consolidate.js +1531 -0
  14. package/dist/commands/curate.js +44 -3
  15. package/dist/commands/db-cli.js +23 -0
  16. package/dist/commands/distill-promotion-policy.js +660 -0
  17. package/dist/commands/distill.js +990 -75
  18. package/dist/commands/eval-cases.js +43 -0
  19. package/dist/commands/events.js +5 -23
  20. package/dist/commands/graph.js +477 -0
  21. package/dist/commands/health.js +400 -0
  22. package/dist/commands/help/help-accept.md +9 -0
  23. package/dist/commands/help/help-improve.md +77 -0
  24. package/dist/commands/help/help-proposals.md +15 -0
  25. package/dist/commands/help/help-propose.md +17 -0
  26. package/dist/commands/help/help-reject.md +8 -0
  27. package/dist/commands/history.js +54 -46
  28. package/dist/commands/improve-profiles.js +146 -0
  29. package/dist/commands/improve-result-file.js +103 -0
  30. package/dist/commands/improve.js +2175 -0
  31. package/dist/commands/info.js +5 -2
  32. package/dist/commands/init.js +50 -2
  33. package/dist/commands/installed-stashes.js +102 -139
  34. package/dist/commands/knowledge.js +136 -0
  35. package/dist/commands/lint/agent-linter.js +49 -0
  36. package/dist/commands/lint/base-linter.js +479 -0
  37. package/dist/commands/lint/command-linter.js +49 -0
  38. package/dist/commands/lint/default-linter.js +16 -0
  39. package/dist/commands/lint/index.js +183 -0
  40. package/dist/commands/lint/knowledge-linter.js +16 -0
  41. package/dist/commands/lint/markdown-insertion.js +343 -0
  42. package/dist/commands/lint/memory-linter.js +61 -0
  43. package/dist/commands/lint/registry.js +36 -0
  44. package/dist/commands/lint/skill-linter.js +45 -0
  45. package/dist/commands/lint/task-linter.js +50 -0
  46. package/dist/commands/lint/types.js +4 -0
  47. package/dist/commands/lint/vault-key-rules.js +139 -0
  48. package/dist/commands/lint/workflow-linter.js +56 -0
  49. package/dist/commands/lint.js +4 -0
  50. package/dist/commands/migration-help.js +5 -2
  51. package/dist/commands/proposal.js +66 -12
  52. package/dist/commands/propose.js +86 -31
  53. package/dist/commands/reflect.js +1119 -73
  54. package/dist/commands/registry-search.js +5 -2
  55. package/dist/commands/remember.js +69 -6
  56. package/dist/commands/schema-repair.js +203 -0
  57. package/dist/commands/search.js +115 -14
  58. package/dist/commands/self-update.js +3 -0
  59. package/dist/commands/show.js +144 -25
  60. package/dist/commands/source-add.js +17 -45
  61. package/dist/commands/source-clone.js +3 -0
  62. package/dist/commands/source-manage.js +14 -19
  63. package/dist/commands/tasks.js +438 -0
  64. package/dist/commands/url-checker.js +42 -0
  65. package/dist/commands/vault.js +130 -77
  66. package/dist/core/action-contributors.js +28 -0
  67. package/dist/core/asset-ref.js +7 -0
  68. package/dist/core/asset-registry.js +7 -16
  69. package/dist/core/asset-serialize.js +88 -0
  70. package/dist/core/asset-spec.js +22 -0
  71. package/dist/core/common.js +157 -0
  72. package/dist/core/concurrent.js +25 -0
  73. package/dist/core/config-io.js +347 -0
  74. package/dist/core/config-migration.js +625 -0
  75. package/dist/core/config-schema.js +501 -0
  76. package/dist/core/config-sources.js +108 -0
  77. package/dist/core/config-types.js +4 -0
  78. package/dist/core/config-walker.js +337 -0
  79. package/dist/core/config.js +327 -987
  80. package/dist/core/errors.js +40 -19
  81. package/dist/core/events.js +91 -138
  82. package/dist/core/file-lock.js +104 -0
  83. package/dist/core/frontmatter.js +3 -6
  84. package/dist/core/lesson-lint.js +3 -0
  85. package/dist/core/markdown.js +20 -0
  86. package/dist/core/memory-belief.js +62 -0
  87. package/dist/core/memory-contradiction-detect.js +274 -0
  88. package/dist/core/memory-improve.js +806 -0
  89. package/dist/core/parse.js +158 -0
  90. package/dist/core/paths.js +326 -14
  91. package/dist/core/proposal-quality-validators.js +364 -0
  92. package/dist/core/proposal-validators.js +69 -0
  93. package/dist/core/proposals.js +498 -42
  94. package/dist/core/state-db.js +927 -0
  95. package/dist/core/text-truncation.js +107 -0
  96. package/dist/core/time.js +54 -0
  97. package/dist/core/warn.js +62 -1
  98. package/dist/core/write-source.js +3 -0
  99. package/dist/indexer/db-backup.js +391 -0
  100. package/dist/indexer/db-search.js +152 -253
  101. package/dist/indexer/db.js +933 -103
  102. package/dist/indexer/ensure-index.js +64 -0
  103. package/dist/indexer/file-context.js +3 -0
  104. package/dist/indexer/graph-boost.js +376 -101
  105. package/dist/indexer/graph-db.js +391 -0
  106. package/dist/indexer/graph-dedup.js +95 -0
  107. package/dist/indexer/graph-extraction.js +550 -124
  108. package/dist/indexer/index-context.js +4 -0
  109. package/dist/indexer/indexer.js +506 -291
  110. package/dist/indexer/llm-cache.js +47 -0
  111. package/dist/indexer/manifest.js +3 -0
  112. package/dist/indexer/matchers.js +148 -160
  113. package/dist/indexer/memory-inference.js +99 -74
  114. package/dist/indexer/metadata-contributors.js +29 -0
  115. package/dist/indexer/metadata.js +255 -196
  116. package/dist/indexer/path-resolver.js +92 -0
  117. package/dist/indexer/project-context.js +192 -0
  118. package/dist/indexer/ranking-contributors.js +331 -0
  119. package/dist/indexer/ranking.js +81 -0
  120. package/dist/indexer/search-fields.js +5 -9
  121. package/dist/indexer/search-hit-enrichers.js +111 -0
  122. package/dist/indexer/search-source.js +44 -10
  123. package/dist/indexer/semantic-status.js +5 -16
  124. package/dist/indexer/staleness-detect.js +447 -0
  125. package/dist/indexer/usage-events.js +12 -9
  126. package/dist/indexer/walker.js +28 -0
  127. package/dist/integrations/agent/builders.js +135 -0
  128. package/dist/integrations/agent/config.js +122 -230
  129. package/dist/integrations/agent/detect.js +3 -0
  130. package/dist/integrations/agent/index.js +7 -13
  131. package/dist/integrations/agent/model-aliases.js +55 -0
  132. package/dist/integrations/agent/profiles.js +70 -5
  133. package/dist/integrations/agent/prompts.js +150 -74
  134. package/dist/integrations/agent/runner.js +151 -0
  135. package/dist/integrations/agent/sdk-runner.js +126 -0
  136. package/dist/integrations/agent/spawn.js +118 -23
  137. package/dist/integrations/github.js +3 -0
  138. package/dist/integrations/lockfile.js +32 -69
  139. package/dist/integrations/session-logs/index.js +68 -0
  140. package/dist/integrations/session-logs/providers/claude-code.js +59 -0
  141. package/dist/integrations/session-logs/providers/opencode.js +55 -0
  142. package/dist/integrations/session-logs/types.js +4 -0
  143. package/dist/llm/call-ai.js +62 -0
  144. package/dist/llm/client.js +72 -124
  145. package/dist/llm/embedder.js +3 -19
  146. package/dist/llm/embedders/cache.js +3 -7
  147. package/dist/llm/embedders/local.js +3 -0
  148. package/dist/llm/embedders/remote.js +20 -8
  149. package/dist/llm/embedders/types.js +3 -7
  150. package/dist/llm/feature-gate.js +89 -48
  151. package/dist/llm/graph-extract.js +676 -70
  152. package/dist/llm/index-passes.js +9 -23
  153. package/dist/llm/memory-infer.js +52 -71
  154. package/dist/llm/metadata-enhance.js +42 -29
  155. package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
  156. package/dist/output/cli-hints-full.md +281 -0
  157. package/dist/output/cli-hints-short.md +65 -0
  158. package/dist/output/cli-hints.js +5 -318
  159. package/dist/output/context.js +3 -0
  160. package/dist/output/renderers.js +223 -256
  161. package/dist/output/shapes.js +150 -105
  162. package/dist/output/text.js +318 -30
  163. package/dist/registry/build-index.js +3 -0
  164. package/dist/registry/create-provider-registry.js +3 -0
  165. package/dist/registry/factory.js +3 -0
  166. package/dist/registry/origin-resolve.js +3 -0
  167. package/dist/registry/providers/index.js +3 -0
  168. package/dist/registry/providers/skills-sh.js +70 -49
  169. package/dist/registry/providers/static-index.js +53 -48
  170. package/dist/registry/providers/types.js +3 -24
  171. package/dist/registry/resolve.js +11 -16
  172. package/dist/registry/types.js +3 -0
  173. package/dist/scripts/migrate-storage.js +17307 -0
  174. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -0
  175. package/dist/scripts/migrations/v16-to-v17.js +141 -0
  176. package/dist/setup/detect.js +3 -0
  177. package/dist/setup/ripgrep-install.js +3 -0
  178. package/dist/setup/ripgrep-resolve.js +3 -0
  179. package/dist/setup/setup.js +775 -37
  180. package/dist/setup/steps.js +3 -15
  181. package/dist/sources/include.js +3 -0
  182. package/dist/sources/provider-factory.js +5 -12
  183. package/dist/sources/provider.js +3 -20
  184. package/dist/sources/providers/filesystem.js +19 -23
  185. package/dist/sources/providers/git.js +7 -5
  186. package/dist/sources/providers/index.js +3 -0
  187. package/dist/sources/providers/install-types.js +3 -13
  188. package/dist/sources/providers/npm.js +3 -4
  189. package/dist/sources/providers/provider-utils.js +3 -0
  190. package/dist/sources/providers/sync-from-ref.js +3 -11
  191. package/dist/sources/providers/tar-utils.js +3 -0
  192. package/dist/sources/providers/website.js +18 -22
  193. package/dist/sources/resolve.js +3 -0
  194. package/dist/sources/types.js +3 -0
  195. package/dist/sources/website-ingest.js +7 -0
  196. package/dist/tasks/backends/cron.js +203 -0
  197. package/dist/tasks/backends/exec-utils.js +28 -0
  198. package/dist/tasks/backends/index.js +24 -0
  199. package/dist/tasks/backends/launchd-template.xml +19 -0
  200. package/dist/tasks/backends/launchd.js +187 -0
  201. package/dist/tasks/backends/schtasks-template.xml +29 -0
  202. package/dist/tasks/backends/schtasks.js +215 -0
  203. package/dist/tasks/parser.js +211 -0
  204. package/dist/tasks/resolveAkmBin.js +87 -0
  205. package/dist/tasks/runner.js +458 -0
  206. package/dist/tasks/schedule.js +211 -0
  207. package/dist/tasks/schema.js +15 -0
  208. package/dist/tasks/validator.js +62 -0
  209. package/dist/version.js +3 -0
  210. package/dist/wiki/index-template.md +12 -0
  211. package/dist/wiki/ingest-workflow-template.md +54 -0
  212. package/dist/wiki/log-template.md +8 -0
  213. package/dist/wiki/schema-template.md +61 -0
  214. package/dist/wiki/wiki-templates.js +15 -0
  215. package/dist/wiki/wiki.js +13 -61
  216. package/dist/workflows/authoring.js +8 -25
  217. package/dist/workflows/cli.js +3 -0
  218. package/dist/workflows/db.js +140 -10
  219. package/dist/workflows/document-cache.js +3 -10
  220. package/dist/workflows/parser.js +3 -0
  221. package/dist/workflows/renderer.js +11 -3
  222. package/dist/workflows/runs.js +62 -91
  223. package/dist/workflows/schema.js +3 -0
  224. package/dist/workflows/scope-key.js +3 -0
  225. package/dist/workflows/validator.js +4 -8
  226. package/dist/workflows/workflow-template.md +24 -0
  227. package/docs/README.md +9 -2
  228. package/docs/data-and-telemetry.md +225 -0
  229. package/docs/migration/release-notes/0.7.0.md +1 -1
  230. package/docs/migration/release-notes/0.7.5.md +2 -2
  231. package/docs/migration/release-notes/0.8.0.md +48 -0
  232. package/docs/migration/v0.7-to-v0.8.md +1307 -0
  233. package/package.json +20 -8
  234. package/.github/LICENSE +0 -374
  235. package/dist/commands/install-audit.js +0 -381
  236. package/dist/templates/wiki-templates.js +0 -100
@@ -1,381 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { filterNonEmptyStrings, toPosix } from "../core/common";
4
- const DEFAULT_INSTALL_AUDIT_CONFIG = {
5
- enabled: true,
6
- blockOnCritical: true,
7
- blockUnlistedRegistries: false,
8
- registryAllowlist: [],
9
- allowedFindings: [],
10
- };
11
- const MAX_SCANNED_FILE_BYTES = 256 * 1024;
12
- const LIFECYCLE_SCRIPT_NAMES = new Set([
13
- "preinstall",
14
- "install",
15
- "postinstall",
16
- "prepublish",
17
- "prepublishOnly",
18
- "prepare",
19
- ]);
20
- const TEXT_FILE_EXTENSIONS = new Set([
21
- ".cjs",
22
- ".cts",
23
- ".js",
24
- ".json",
25
- ".jsonc",
26
- ".jsx",
27
- ".mjs",
28
- ".md",
29
- ".ps1",
30
- ".py",
31
- ".rb",
32
- ".sh",
33
- ".toml",
34
- ".ts",
35
- ".tsx",
36
- ".txt",
37
- ".yaml",
38
- ".yml",
39
- ]);
40
- const BLOCKED_PACKAGE_DIRECTORIES = new Set(["node_modules", "venv", ".venv", "site-packages"]);
41
- const CONTENT_RULES = [
42
- {
43
- id: "prompt-ignore-previous-instructions",
44
- severity: "high",
45
- category: "prompt-injection",
46
- message: "Contains instructions to ignore prior prompts or instructions.",
47
- pattern: /\b(ignore|disregard|forget)\b[^.\n]{0,100}\b(previous|prior|earlier)\b[^.\n]{0,100}\b(instructions?|prompts?|messages?)\b/i,
48
- },
49
- {
50
- id: "prompt-reveal-hidden-secrets",
51
- severity: "critical",
52
- category: "prompt-injection",
53
- message: "Contains instructions to reveal hidden prompts or secrets.",
54
- pattern: /\b(?:reveal|print|dump|show|output|return|exfiltrat(?:e|ion))\b[^.\n]{0,60}\b(?:your|the)\b[^.\n]{0,40}\b(system prompt|hidden instructions?|developer message|api key|token|secret|password)\b/i,
55
- },
56
- {
57
- id: "prompt-bypass-guardrails",
58
- severity: "high",
59
- category: "prompt-injection",
60
- message: "Contains instructions to bypass safety or security controls.",
61
- pattern: /\b(bypass|disable|ignore)\b[^.\n]{0,100}\b(safety|security|guardrails|restrictions|policies)\b/i,
62
- },
63
- {
64
- id: "remote-shell-pipe",
65
- severity: "critical",
66
- category: "malicious-code",
67
- message: "Downloads remote content and pipes it directly into a shell.",
68
- pattern: /\b(curl|wget)\b[^\n|]{0,200}\|\s*(sh|bash|zsh)\b/i,
69
- },
70
- {
71
- id: "powershell-download-exec",
72
- severity: "critical",
73
- category: "malicious-code",
74
- message: "Downloads remote content and executes it in PowerShell.",
75
- pattern: /\b(Invoke-WebRequest|iwr|curl)\b[^\n|]{0,200}\|\s*(iex|Invoke-Expression)\b/i,
76
- },
77
- {
78
- id: "powershell-encoded-command",
79
- severity: "critical",
80
- category: "malicious-code",
81
- message: "Uses an encoded PowerShell command.",
82
- pattern: /\bpowershell(?:\.exe)?\b[^\n]{0,120}\s-(?:enc|encodedcommand)\b/i,
83
- },
84
- {
85
- id: "credential-exfiltration-language",
86
- severity: "high",
87
- category: "malicious-code",
88
- message: "Contains language associated with credential or secret exfiltration.",
89
- pattern: /\b(exfiltrat(?:e|ion)|harvest|steal)\b[^.\n]{0,120}\b(credentials?|tokens?|secrets?|ssh keys?|passwords?|cookies?)\b/i,
90
- },
91
- ];
92
- export function resolveInstallAuditConfig(config) {
93
- const installAudit = config?.security?.installAudit;
94
- const allowlist = filterNonEmptyStrings(installAudit?.registryAllowlist) ??
95
- filterNonEmptyStrings(installAudit?.registryWhitelist) ??
96
- [];
97
- return {
98
- enabled: installAudit?.enabled ?? DEFAULT_INSTALL_AUDIT_CONFIG.enabled,
99
- blockOnCritical: installAudit?.blockOnCritical ?? DEFAULT_INSTALL_AUDIT_CONFIG.blockOnCritical,
100
- blockUnlistedRegistries: installAudit?.blockUnlistedRegistries ?? DEFAULT_INSTALL_AUDIT_CONFIG.blockUnlistedRegistries,
101
- registryAllowlist: allowlist.map((entry) => entry.trim().toLowerCase()),
102
- allowedFindings: installAudit?.allowedFindings ?? DEFAULT_INSTALL_AUDIT_CONFIG.allowedFindings,
103
- };
104
- }
105
- export function enforceRegistryInstallPolicy(registryLabels, config, ref) {
106
- const resolved = resolveInstallAuditConfig(config);
107
- if (!resolved.blockUnlistedRegistries)
108
- return;
109
- if (resolved.registryAllowlist.length === 0) {
110
- throw new Error(`Install blocked for ${ref}: no registries are allowlisted. Configure security.installAudit.registryAllowlist or disable security.installAudit.blockUnlistedRegistries.`);
111
- }
112
- const matched = registryLabels.some((label) => resolved.registryAllowlist.includes(label.toLowerCase()));
113
- if (matched)
114
- return;
115
- throw new Error(`Install blocked for ${ref}: registry is not allowlisted. Allowed: ${resolved.registryAllowlist.join(", ")}. Seen: ${registryLabels.join(", ")}.`);
116
- }
117
- export function auditInstallCandidate(input) {
118
- const resolved = resolveInstallAuditConfig(input.config);
119
- if (!resolved.enabled) {
120
- return {
121
- enabled: false,
122
- passed: true,
123
- blocked: false,
124
- trusted: false,
125
- registryLabels: [...input.registryLabels],
126
- findings: [],
127
- scannedFiles: 0,
128
- scannedBytes: 0,
129
- summary: buildSummary([]),
130
- };
131
- }
132
- const findings = [];
133
- const counters = { scannedFiles: 0, scannedBytes: 0 };
134
- scanDirectory(input.rootDir, input.rootDir, findings, counters);
135
- const { findings: activeFindings, waivedFindings } = splitAllowedFindings(findings, input.ref, resolved.allowedFindings);
136
- const summary = buildSummary(activeFindings);
137
- const blocked = !input.trustThisInstall && resolved.blockOnCritical && summary.critical > 0;
138
- return {
139
- enabled: true,
140
- passed: activeFindings.length === 0,
141
- blocked,
142
- trusted: Boolean(input.trustThisInstall),
143
- registryLabels: [...input.registryLabels],
144
- findings: activeFindings,
145
- scannedFiles: counters.scannedFiles,
146
- scannedBytes: counters.scannedBytes,
147
- summary,
148
- ...(waivedFindings.length > 0 ? { waivedFindings } : {}),
149
- };
150
- }
151
- export function formatInstallAuditFailure(ref, report) {
152
- const lines = [`Security audit failed for ${ref}.`, formatInstallAuditSummary(report)];
153
- for (const finding of report.findings.slice(0, 5)) {
154
- lines.push(`- [${finding.severity}] ${finding.message}${finding.file ? ` (${finding.file})` : ""}`);
155
- }
156
- if (report.findings.length > 5) {
157
- lines.push(`- ${report.findings.length - 5} more finding(s) omitted`);
158
- }
159
- lines.push("Disable blocking with `security.installAudit.blockOnCritical = false`, or disable audits with `security.installAudit.enabled = false`." +
160
- " Or pass --trust on a one-off 'akm add' to bypass this audit for this install only.");
161
- return lines.join("\n");
162
- }
163
- export function formatInstallAuditSummary(report) {
164
- if (!report.enabled)
165
- return "Audit: disabled";
166
- const severitySummary = [];
167
- if (report.summary.critical > 0)
168
- severitySummary.push(`${report.summary.critical} critical`);
169
- if (report.summary.high > 0)
170
- severitySummary.push(`${report.summary.high} high`);
171
- if (report.summary.moderate > 0)
172
- severitySummary.push(`${report.summary.moderate} moderate`);
173
- if (report.summary.low > 0)
174
- severitySummary.push(`${report.summary.low} low`);
175
- const detail = severitySummary.length > 0 ? severitySummary.join(", ") : "no findings";
176
- const status = report.blocked ? "blocked" : report.passed ? "passed" : report.trusted ? "trusted" : "warnings";
177
- const waived = report.waivedFindings?.length ? `; waived ${report.waivedFindings.length}` : "";
178
- return `Audit: ${status} (${detail}; scanned ${report.scannedFiles} file${report.scannedFiles === 1 ? "" : "s"}${waived})`;
179
- }
180
- export function deriveRegistryLabels(input) {
181
- const labels = new Set();
182
- labels.add(input.source);
183
- if (input.source === "github")
184
- labels.add("github.com");
185
- if (input.source === "npm")
186
- labels.add("npm");
187
- addUrlLabels(labels, input.artifactUrl);
188
- addUrlLabels(labels, input.gitUrl);
189
- if (input.source === "github" && input.ref.startsWith("github:")) {
190
- labels.add("github");
191
- }
192
- return [...labels];
193
- }
194
- function scanDirectory(dir, rootDir, findings, counters) {
195
- let entries;
196
- try {
197
- entries = fs.readdirSync(dir, { withFileTypes: true });
198
- }
199
- catch {
200
- return;
201
- }
202
- for (const entry of entries) {
203
- if (entry.name === ".git")
204
- continue;
205
- const fullPath = path.join(dir, entry.name);
206
- if (entry.isDirectory()) {
207
- if (BLOCKED_PACKAGE_DIRECTORIES.has(entry.name)) {
208
- const relativePath = path.relative(rootDir, fullPath) || entry.name;
209
- findings.push({
210
- id: "bundled-package-directory",
211
- severity: "critical",
212
- category: "vendored-dependency",
213
- message: `Contains bundled dependency directory "${entry.name}".`,
214
- file: relativePath,
215
- snippet: relativePath,
216
- });
217
- continue;
218
- }
219
- scanDirectory(fullPath, rootDir, findings, counters);
220
- continue;
221
- }
222
- if (!entry.isFile())
223
- continue;
224
- scanFile(fullPath, rootDir, findings, counters);
225
- }
226
- }
227
- function scanFile(filePath, rootDir, findings, counters) {
228
- const ext = path.extname(filePath).toLowerCase();
229
- const basename = path.basename(filePath).toLowerCase();
230
- if (basename !== "package.json" && !TEXT_FILE_EXTENSIONS.has(ext))
231
- return;
232
- let fileSize;
233
- try {
234
- fileSize = fs.statSync(filePath).size;
235
- }
236
- catch {
237
- return;
238
- }
239
- const readSize = Math.min(fileSize, MAX_SCANNED_FILE_BYTES);
240
- const buf = Buffer.alloc(readSize);
241
- let bytesRead;
242
- try {
243
- const fd = fs.openSync(filePath, "r");
244
- try {
245
- bytesRead = fs.readSync(fd, buf, 0, readSize, 0);
246
- }
247
- finally {
248
- fs.closeSync(fd);
249
- }
250
- }
251
- catch {
252
- return;
253
- }
254
- if (bytesRead === 0)
255
- return;
256
- const bytes = buf.subarray(0, bytesRead);
257
- if (bytes.includes(0))
258
- return;
259
- counters.scannedFiles += 1;
260
- counters.scannedBytes += bytesRead;
261
- const content = bytes.toString("utf8");
262
- const relativePath = path.relative(rootDir, filePath) || path.basename(filePath);
263
- const genericContent = basename === "package.json" ? stripPackageJsonScripts(content) : content;
264
- for (const rule of CONTENT_RULES) {
265
- const match = genericContent.match(rule.pattern);
266
- if (!match)
267
- continue;
268
- findings.push({
269
- id: rule.id,
270
- severity: rule.severity,
271
- category: rule.category,
272
- message: rule.message,
273
- file: relativePath,
274
- snippet: clipSnippet(match[0]),
275
- });
276
- }
277
- if (basename === "package.json") {
278
- scanPackageJson(content, relativePath, findings);
279
- }
280
- }
281
- function stripPackageJsonScripts(content) {
282
- let parsed;
283
- try {
284
- parsed = JSON.parse(content);
285
- }
286
- catch {
287
- return content;
288
- }
289
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
290
- return content;
291
- const packageJson = { ...parsed };
292
- delete packageJson.scripts;
293
- return JSON.stringify(packageJson, null, 2);
294
- }
295
- function scanPackageJson(content, relativePath, findings) {
296
- let parsed;
297
- try {
298
- parsed = JSON.parse(content);
299
- }
300
- catch {
301
- return;
302
- }
303
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
304
- return;
305
- const scripts = parsed.scripts;
306
- if (typeof scripts !== "object" || scripts === null || Array.isArray(scripts))
307
- return;
308
- for (const [name, command] of Object.entries(scripts)) {
309
- if (!LIFECYCLE_SCRIPT_NAMES.has(name) || typeof command !== "string")
310
- continue;
311
- for (const rule of CONTENT_RULES) {
312
- if (!rule.pattern.test(command))
313
- continue;
314
- findings.push({
315
- id: `lifecycle-${name}-${rule.id}`,
316
- severity: rule.severity,
317
- category: "install-script",
318
- message: `Lifecycle script "${name}" is suspicious: ${rule.message.toLowerCase()}`,
319
- file: relativePath,
320
- snippet: clipSnippet(command),
321
- });
322
- }
323
- }
324
- }
325
- function clipSnippet(value) {
326
- const normalized = value.replace(/\s+/g, " ").trim();
327
- return normalized.length <= 140 ? normalized : `${normalized.slice(0, 137)}...`;
328
- }
329
- function buildSummary(findings) {
330
- const summary = { low: 0, moderate: 0, high: 0, critical: 0, total: findings.length };
331
- for (const finding of findings) {
332
- summary[finding.severity] += 1;
333
- }
334
- return summary;
335
- }
336
- function splitAllowedFindings(findings, ref, allowedFindings) {
337
- const active = [];
338
- const waived = [];
339
- for (const finding of findings) {
340
- if (matchesAllowedFinding(finding, ref, allowedFindings)) {
341
- waived.push(finding);
342
- continue;
343
- }
344
- active.push(finding);
345
- }
346
- return { findings: active, waivedFindings: waived };
347
- }
348
- function matchesAllowedFinding(finding, ref, allowedFindings) {
349
- // Normalize paths so a waiver written against `scripts/setup.sh` matches
350
- // a finding emitted as `./scripts/setup.sh` or `scripts//setup.sh`. On
351
- // Windows we also fold case, mirroring `isWithin`'s comparison rules.
352
- const findingPathNormalized = normalizeWaiverPath(finding.file);
353
- return allowedFindings.some((allowed) => {
354
- if (allowed.id !== finding.id)
355
- return false;
356
- if (allowed.ref && allowed.ref !== ref)
357
- return false;
358
- if (allowed.path && normalizeWaiverPath(allowed.path) !== findingPathNormalized)
359
- return false;
360
- return true;
361
- });
362
- }
363
- function normalizeWaiverPath(value) {
364
- if (!value)
365
- return value;
366
- // Strip a leading `./` and POSIX-ify after path.normalize so Windows path
367
- // separators don't trigger spurious mismatches.
368
- const normalized = toPosix(path.normalize(value)).replace(/^\.\/+/, "");
369
- return process.platform === "win32" ? normalized.toLowerCase() : normalized;
370
- }
371
- function addUrlLabels(labels, rawUrl) {
372
- if (!rawUrl)
373
- return;
374
- try {
375
- const parsed = new URL(rawUrl);
376
- labels.add(parsed.hostname.toLowerCase());
377
- }
378
- catch {
379
- // Ignore non-URL refs (for example git@host:path)
380
- }
381
- }
@@ -1,100 +0,0 @@
1
- /**
2
- * Scaffolded content for a fresh `wikis/<name>/` directory.
3
- *
4
- * Inlined as TypeScript constants so they ship with the published bundle
5
- * (the build step is `tsc` — non-TS files are not copied to dist).
6
- *
7
- * The scaffold is deliberately minimal: akm does not prescribe conventions
8
- * beyond the three-layer layout. `schema.md` is the per-wiki rulebook the
9
- * agent reads first; authors customise it freely.
10
- */
11
- export function buildSchemaMd(wikiName) {
12
- return `---
13
- description: Rules that govern this wiki. Read before ingesting, searching, or editing pages.
14
- wikiRole: schema
15
- ---
16
-
17
- # ${wikiName} wiki schema
18
-
19
- This wiki follows the three-layer pattern:
20
-
21
- - \`raw/\` — immutable ingested sources (never edit)
22
- - \`<page>.md\` and \`<topic>/<page>.md\` — agent-authored pages
23
- - \`schema.md\` (this file), \`index.md\`, \`log.md\` — wiki-level metadata
24
-
25
- ## Page frontmatter
26
-
27
- Every page should carry frontmatter so akm can index and link it:
28
-
29
- \`\`\`yaml
30
- ---
31
- description: one-sentence summary used in search and lint
32
- pageKind: entity | concept | question | note | <your-custom-kind>
33
- xrefs:
34
- - wiki:${wikiName}/other-page
35
- sources:
36
- - raw/<slug>.md
37
- ---
38
- \`\`\`
39
-
40
- \`pageKind\` accepts any non-empty string. Add new categories freely; they
41
- will surface in \`index.md\` as new sections after the next \`akm index\` run.
42
-
43
- ## Three operations
44
-
45
- ### Ingest
46
-
47
- 1. Copy the new source into \`raw/\` with \`akm wiki stash ${wikiName} <path>\`.
48
- 2. Find related pages: \`akm wiki search ${wikiName} "<terms>"\`.
49
- 3. For each related page: append a section, note a contradiction, or create a
50
- new page. Update xrefs on both sides.
51
- 4. Cite the raw source in each touched page's \`sources:\` frontmatter.
52
- 5. Append one entry to \`log.md\` describing what was assimilated.
53
-
54
- ### Query
55
-
56
- 1. \`akm wiki search ${wikiName} "<question>"\` — find candidate pages.
57
- 2. \`akm show wiki:${wikiName}/<page>\` — read the top hits.
58
- 3. Compose the answer from the wiki; cite raw sources only when the wiki
59
- points at them.
60
-
61
- ### Lint
62
-
63
- 1. \`akm wiki lint ${wikiName}\` — deterministic structural checks.
64
- 2. Resolve each finding: link orphans, fix broken xrefs, add descriptions,
65
- cite uncited raws, refresh the index.
66
-
67
- ## Hard rules
68
-
69
- - \`raw/\` is immutable. Never edit ingested sources.
70
- - Cross-references must point at pages that actually exist.
71
- - Prefer appending to an existing page over duplicating one.
72
- - Cite the raw source id (e.g. \`raw/2026-04-foo.md\`) when copying claims.
73
- `;
74
- }
75
- export function buildIndexMd(wikiName) {
76
- return `---
77
- description: Catalog of pages in the ${wikiName} wiki. Regenerated by \`akm index\`.
78
- wikiRole: index
79
- ---
80
-
81
- # ${wikiName} — index
82
-
83
- _This file is regenerated on every \`akm index\` run. Manual edits are
84
- preserved until the next regeneration, then replaced._
85
-
86
- _(no pages yet — create one with your editor, or ingest a source with \`akm
87
- wiki stash ${wikiName} <path>\`.)_
88
- `;
89
- }
90
- export function buildLogMd(wikiName) {
91
- return `---
92
- description: Append-only log for the ${wikiName} wiki. Newest entries at the top.
93
- wikiRole: log
94
- ---
95
-
96
- # ${wikiName} — log
97
-
98
- _Each entry: ISO date, operation, brief summary._
99
- `;
100
- }