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.
- package/{.github/CHANGELOG.md → CHANGELOG.md} +113 -2
- package/README.md +20 -4
- package/SECURITY.md +93 -0
- package/dist/cli/config-migrate.js +144 -0
- package/dist/cli/config-validate.js +39 -0
- package/dist/cli/confirm.js +73 -0
- package/dist/cli/parse-args.js +133 -0
- package/dist/cli.js +1995 -551
- package/dist/commands/agent-dispatch.js +110 -0
- package/dist/commands/agent-support.js +68 -0
- package/dist/commands/completions.js +3 -0
- package/dist/commands/config-cli.js +130 -534
- package/dist/commands/consolidate.js +1531 -0
- package/dist/commands/curate.js +44 -3
- package/dist/commands/db-cli.js +23 -0
- package/dist/commands/distill-promotion-policy.js +660 -0
- package/dist/commands/distill.js +990 -75
- package/dist/commands/eval-cases.js +43 -0
- package/dist/commands/events.js +5 -23
- package/dist/commands/graph.js +477 -0
- package/dist/commands/health.js +400 -0
- package/dist/commands/help/help-accept.md +9 -0
- package/dist/commands/help/help-improve.md +77 -0
- package/dist/commands/help/help-proposals.md +15 -0
- package/dist/commands/help/help-propose.md +17 -0
- package/dist/commands/help/help-reject.md +8 -0
- package/dist/commands/history.js +54 -46
- package/dist/commands/improve-profiles.js +146 -0
- package/dist/commands/improve-result-file.js +103 -0
- package/dist/commands/improve.js +2175 -0
- package/dist/commands/info.js +5 -2
- package/dist/commands/init.js +50 -2
- package/dist/commands/installed-stashes.js +102 -139
- package/dist/commands/knowledge.js +136 -0
- package/dist/commands/lint/agent-linter.js +49 -0
- package/dist/commands/lint/base-linter.js +479 -0
- package/dist/commands/lint/command-linter.js +49 -0
- package/dist/commands/lint/default-linter.js +16 -0
- package/dist/commands/lint/index.js +183 -0
- package/dist/commands/lint/knowledge-linter.js +16 -0
- package/dist/commands/lint/markdown-insertion.js +343 -0
- package/dist/commands/lint/memory-linter.js +61 -0
- package/dist/commands/lint/registry.js +36 -0
- package/dist/commands/lint/skill-linter.js +45 -0
- package/dist/commands/lint/task-linter.js +50 -0
- package/dist/commands/lint/types.js +4 -0
- package/dist/commands/lint/vault-key-rules.js +139 -0
- package/dist/commands/lint/workflow-linter.js +56 -0
- package/dist/commands/lint.js +4 -0
- package/dist/commands/migration-help.js +5 -2
- package/dist/commands/proposal.js +66 -12
- package/dist/commands/propose.js +86 -31
- package/dist/commands/reflect.js +1119 -73
- package/dist/commands/registry-search.js +5 -2
- package/dist/commands/remember.js +69 -6
- package/dist/commands/schema-repair.js +203 -0
- package/dist/commands/search.js +115 -14
- package/dist/commands/self-update.js +3 -0
- package/dist/commands/show.js +144 -25
- package/dist/commands/source-add.js +17 -45
- package/dist/commands/source-clone.js +3 -0
- package/dist/commands/source-manage.js +14 -19
- package/dist/commands/tasks.js +438 -0
- package/dist/commands/url-checker.js +42 -0
- package/dist/commands/vault.js +130 -77
- package/dist/core/action-contributors.js +28 -0
- package/dist/core/asset-ref.js +7 -0
- package/dist/core/asset-registry.js +7 -16
- package/dist/core/asset-serialize.js +88 -0
- package/dist/core/asset-spec.js +22 -0
- package/dist/core/common.js +157 -0
- package/dist/core/concurrent.js +25 -0
- package/dist/core/config-io.js +347 -0
- package/dist/core/config-migration.js +625 -0
- package/dist/core/config-schema.js +501 -0
- package/dist/core/config-sources.js +108 -0
- package/dist/core/config-types.js +4 -0
- package/dist/core/config-walker.js +337 -0
- package/dist/core/config.js +327 -987
- package/dist/core/errors.js +40 -19
- package/dist/core/events.js +91 -138
- package/dist/core/file-lock.js +104 -0
- package/dist/core/frontmatter.js +3 -6
- package/dist/core/lesson-lint.js +3 -0
- package/dist/core/markdown.js +20 -0
- package/dist/core/memory-belief.js +62 -0
- package/dist/core/memory-contradiction-detect.js +274 -0
- package/dist/core/memory-improve.js +806 -0
- package/dist/core/parse.js +158 -0
- package/dist/core/paths.js +326 -14
- package/dist/core/proposal-quality-validators.js +364 -0
- package/dist/core/proposal-validators.js +69 -0
- package/dist/core/proposals.js +498 -42
- package/dist/core/state-db.js +927 -0
- package/dist/core/text-truncation.js +107 -0
- package/dist/core/time.js +54 -0
- package/dist/core/warn.js +62 -1
- package/dist/core/write-source.js +3 -0
- package/dist/indexer/db-backup.js +391 -0
- package/dist/indexer/db-search.js +152 -253
- package/dist/indexer/db.js +933 -103
- package/dist/indexer/ensure-index.js +64 -0
- package/dist/indexer/file-context.js +3 -0
- package/dist/indexer/graph-boost.js +376 -101
- package/dist/indexer/graph-db.js +391 -0
- package/dist/indexer/graph-dedup.js +95 -0
- package/dist/indexer/graph-extraction.js +550 -124
- package/dist/indexer/index-context.js +4 -0
- package/dist/indexer/indexer.js +506 -291
- package/dist/indexer/llm-cache.js +47 -0
- package/dist/indexer/manifest.js +3 -0
- package/dist/indexer/matchers.js +148 -160
- package/dist/indexer/memory-inference.js +99 -74
- package/dist/indexer/metadata-contributors.js +29 -0
- package/dist/indexer/metadata.js +255 -196
- package/dist/indexer/path-resolver.js +92 -0
- package/dist/indexer/project-context.js +192 -0
- package/dist/indexer/ranking-contributors.js +331 -0
- package/dist/indexer/ranking.js +81 -0
- package/dist/indexer/search-fields.js +5 -9
- package/dist/indexer/search-hit-enrichers.js +111 -0
- package/dist/indexer/search-source.js +44 -10
- package/dist/indexer/semantic-status.js +5 -16
- package/dist/indexer/staleness-detect.js +447 -0
- package/dist/indexer/usage-events.js +12 -9
- package/dist/indexer/walker.js +28 -0
- package/dist/integrations/agent/builders.js +135 -0
- package/dist/integrations/agent/config.js +122 -230
- package/dist/integrations/agent/detect.js +3 -0
- package/dist/integrations/agent/index.js +7 -13
- package/dist/integrations/agent/model-aliases.js +55 -0
- package/dist/integrations/agent/profiles.js +70 -5
- package/dist/integrations/agent/prompts.js +150 -74
- package/dist/integrations/agent/runner.js +151 -0
- package/dist/integrations/agent/sdk-runner.js +126 -0
- package/dist/integrations/agent/spawn.js +118 -23
- package/dist/integrations/github.js +3 -0
- package/dist/integrations/lockfile.js +32 -69
- package/dist/integrations/session-logs/index.js +68 -0
- package/dist/integrations/session-logs/providers/claude-code.js +59 -0
- package/dist/integrations/session-logs/providers/opencode.js +55 -0
- package/dist/integrations/session-logs/types.js +4 -0
- package/dist/llm/call-ai.js +62 -0
- package/dist/llm/client.js +72 -124
- package/dist/llm/embedder.js +3 -19
- package/dist/llm/embedders/cache.js +3 -7
- package/dist/llm/embedders/local.js +3 -0
- package/dist/llm/embedders/remote.js +20 -8
- package/dist/llm/embedders/types.js +3 -7
- package/dist/llm/feature-gate.js +89 -48
- package/dist/llm/graph-extract.js +676 -70
- package/dist/llm/index-passes.js +9 -23
- package/dist/llm/memory-infer.js +52 -71
- package/dist/llm/metadata-enhance.js +42 -29
- package/dist/llm/prompts/graph-extract-user-prompt.md +35 -0
- package/dist/output/cli-hints-full.md +281 -0
- package/dist/output/cli-hints-short.md +65 -0
- package/dist/output/cli-hints.js +5 -318
- package/dist/output/context.js +3 -0
- package/dist/output/renderers.js +223 -256
- package/dist/output/shapes.js +150 -105
- package/dist/output/text.js +318 -30
- package/dist/registry/build-index.js +3 -0
- package/dist/registry/create-provider-registry.js +3 -0
- package/dist/registry/factory.js +3 -0
- package/dist/registry/origin-resolve.js +3 -0
- package/dist/registry/providers/index.js +3 -0
- package/dist/registry/providers/skills-sh.js +70 -49
- package/dist/registry/providers/static-index.js +53 -48
- package/dist/registry/providers/types.js +3 -24
- package/dist/registry/resolve.js +11 -16
- package/dist/registry/types.js +3 -0
- package/dist/scripts/migrate-storage.js +17307 -0
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +8900 -0
- package/dist/scripts/migrations/v16-to-v17.js +141 -0
- package/dist/setup/detect.js +3 -0
- package/dist/setup/ripgrep-install.js +3 -0
- package/dist/setup/ripgrep-resolve.js +3 -0
- package/dist/setup/setup.js +775 -37
- package/dist/setup/steps.js +3 -15
- package/dist/sources/include.js +3 -0
- package/dist/sources/provider-factory.js +5 -12
- package/dist/sources/provider.js +3 -20
- package/dist/sources/providers/filesystem.js +19 -23
- package/dist/sources/providers/git.js +7 -5
- package/dist/sources/providers/index.js +3 -0
- package/dist/sources/providers/install-types.js +3 -13
- package/dist/sources/providers/npm.js +3 -4
- package/dist/sources/providers/provider-utils.js +3 -0
- package/dist/sources/providers/sync-from-ref.js +3 -11
- package/dist/sources/providers/tar-utils.js +3 -0
- package/dist/sources/providers/website.js +18 -22
- package/dist/sources/resolve.js +3 -0
- package/dist/sources/types.js +3 -0
- package/dist/sources/website-ingest.js +7 -0
- package/dist/tasks/backends/cron.js +203 -0
- package/dist/tasks/backends/exec-utils.js +28 -0
- package/dist/tasks/backends/index.js +24 -0
- package/dist/tasks/backends/launchd-template.xml +19 -0
- package/dist/tasks/backends/launchd.js +187 -0
- package/dist/tasks/backends/schtasks-template.xml +29 -0
- package/dist/tasks/backends/schtasks.js +215 -0
- package/dist/tasks/parser.js +211 -0
- package/dist/tasks/resolveAkmBin.js +87 -0
- package/dist/tasks/runner.js +458 -0
- package/dist/tasks/schedule.js +211 -0
- package/dist/tasks/schema.js +15 -0
- package/dist/tasks/validator.js +62 -0
- package/dist/version.js +3 -0
- package/dist/wiki/index-template.md +12 -0
- package/dist/wiki/ingest-workflow-template.md +54 -0
- package/dist/wiki/log-template.md +8 -0
- package/dist/wiki/schema-template.md +61 -0
- package/dist/wiki/wiki-templates.js +15 -0
- package/dist/wiki/wiki.js +13 -61
- package/dist/workflows/authoring.js +8 -25
- package/dist/workflows/cli.js +3 -0
- package/dist/workflows/db.js +140 -10
- package/dist/workflows/document-cache.js +3 -10
- package/dist/workflows/parser.js +3 -0
- package/dist/workflows/renderer.js +11 -3
- package/dist/workflows/runs.js +62 -91
- package/dist/workflows/schema.js +3 -0
- package/dist/workflows/scope-key.js +3 -0
- package/dist/workflows/validator.js +4 -8
- package/dist/workflows/workflow-template.md +24 -0
- package/docs/README.md +9 -2
- package/docs/data-and-telemetry.md +225 -0
- package/docs/migration/release-notes/0.7.0.md +1 -1
- package/docs/migration/release-notes/0.7.5.md +2 -2
- package/docs/migration/release-notes/0.8.0.md +48 -0
- package/docs/migration/v0.7-to-v0.8.md +1307 -0
- package/package.json +20 -8
- package/.github/LICENSE +0 -374
- package/dist/commands/install-audit.js +0 -381
- 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
|
-
}
|