akm-cli 0.9.0-rc.8 → 0.9.0
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/CHANGELOG.md +1063 -44
- package/README.md +51 -25
- package/SECURITY.md +14 -1
- package/STABILITY.md +497 -0
- package/dist/akm +148 -35
- package/dist/{akm-migrate-storage → akm-migrate} +6 -9
- package/dist/assets/hints/cli-hints-full.md +223 -95
- package/dist/assets/hints/cli-hints-short.md +85 -22
- package/dist/assets/improve-strategies/default.json +1 -1
- package/dist/assets/improve-strategies/reflect-distill.json +1 -1
- package/dist/assets/prompts/memory-infer-user.md +2 -3
- package/dist/assets/stash-skeleton/README.md +6 -5
- package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -0
- package/dist/assets/stash-skeleton/facts/conventions/organization.md +20 -9
- package/dist/assets/tasks/core/extract.yml +1 -1
- package/dist/assets/tasks/core/version-check.yml +1 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
- package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
- package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
- package/dist/assets/templates/html/health.html +1 -3
- package/dist/assets/workflows/workflow-template.md +32 -15
- package/dist/cli/invocation.js +40 -15
- package/dist/cli/parse-args.js +0 -22
- package/dist/cli/retired-commands.js +121 -0
- package/dist/cli/shared.js +154 -22
- package/dist/cli/unknown-flags.js +236 -0
- package/dist/cli-node.mjs +2 -1
- package/dist/cli.js +696 -258
- package/dist/commands/agent/agent-dispatch.js +14 -3
- package/dist/commands/agent/contribute-cli.js +73 -88
- package/dist/commands/completions.js +79 -22
- package/dist/commands/config-cli.js +17 -150
- package/dist/commands/env/env-cli.js +59 -143
- package/dist/commands/env/env.js +12 -163
- package/dist/commands/env/marker-path.js +6 -0
- package/dist/commands/env/secret-cli.js +36 -66
- package/dist/commands/env/secret.js +24 -57
- package/dist/commands/feedback-cli.js +141 -87
- package/dist/commands/health/accept-rate.js +58 -0
- package/dist/commands/health/advisories.js +3 -4
- package/dist/commands/health/checks.js +85 -23
- package/dist/commands/health/html-report.js +7 -10
- package/dist/commands/health/improve-metrics.js +25 -83
- package/dist/commands/health/md-report.js +5 -9
- package/dist/commands/health/metrics.js +62 -20
- package/dist/commands/health/renderers.js +47 -0
- package/dist/commands/health/report-view-model.js +4 -5
- package/dist/commands/health/stash-exposure.js +1 -1
- package/dist/commands/health/surfaces.js +3 -48
- package/dist/commands/health/task-runs.js +3 -67
- package/dist/commands/health/types-improve.js +7 -0
- package/dist/commands/health.js +99 -28
- package/dist/commands/improve/anti-collapse.js +2 -2
- package/dist/commands/improve/autonomy-gate.js +68 -0
- package/dist/commands/improve/collapse-detector.js +41 -40
- package/dist/commands/improve/consolidate/eligibility.js +1 -23
- package/dist/commands/improve/consolidate/merge.js +4 -0
- package/dist/commands/improve/consolidate.js +140 -1000
- package/dist/commands/improve/distill/promote-memory.js +12 -12
- package/dist/commands/improve/distill/quality-gate.js +6 -6
- package/dist/commands/improve/distill.js +58 -69
- package/dist/commands/improve/eligibility.js +105 -57
- package/dist/commands/improve/extract-cli.js +14 -133
- package/dist/commands/improve/improve-cli.js +98 -114
- package/dist/commands/improve/improve-result-file.js +1 -28
- package/dist/commands/improve/improve-strategies.js +8 -5
- package/dist/commands/improve/improve.js +128 -91
- package/dist/commands/improve/loop-stages.js +182 -20
- package/dist/commands/improve/memory/derived-ref.js +45 -43
- package/dist/commands/improve/memory/memory-belief.js +1 -1
- package/dist/commands/improve/memory/memory-contradiction-detect.js +4 -12
- package/dist/commands/improve/memory/memory-improve.js +6 -5
- package/dist/commands/improve/outcome-loop.js +22 -65
- package/dist/commands/improve/preparation.js +114 -123
- package/dist/commands/improve/proactive-maintenance.js +2 -5
- package/dist/commands/improve/reflect.js +56 -160
- package/dist/commands/improve/salience.js +11 -122
- package/dist/commands/improve/source-identity.js +10 -38
- package/dist/commands/lint/base-linter.js +20 -124
- package/dist/commands/lint/env-key-rules.js +31 -47
- package/dist/commands/lint/index.js +249 -43
- package/dist/commands/{events.js → log.js} +33 -38
- package/dist/commands/migrate-cli.js +92 -12
- package/dist/commands/migration-tool.js +46 -0
- package/dist/commands/observability-cli.js +70 -209
- package/dist/commands/proposal/drain.js +101 -29
- package/dist/commands/proposal/proposal-cli.js +76 -48
- package/dist/commands/proposal/proposal.js +54 -18
- package/dist/commands/proposal/propose-cli.js +88 -0
- package/dist/commands/proposal/propose.js +23 -15
- package/dist/commands/proposal/repository.js +701 -278
- package/dist/commands/proposal/validators/proposal-quality-validators.js +2 -8
- package/dist/commands/proposal/validators/proposal-validators.js +55 -7
- package/dist/commands/proposal/validators/proposals.js +4 -7
- package/dist/commands/read/curate.js +34 -53
- package/dist/commands/read/knowledge.js +150 -95
- package/dist/commands/read/registry-search.js +2 -2
- package/dist/commands/read/remember-cli.js +42 -15
- package/dist/commands/read/search-cli.js +180 -78
- package/dist/commands/read/search.js +58 -43
- package/dist/commands/read/show.js +197 -141
- package/dist/commands/registry-cli.js +12 -51
- package/dist/commands/remember.js +14 -57
- package/dist/commands/sources/add-cli.js +100 -31
- package/dist/commands/sources/bundle-cli.js +166 -0
- package/dist/commands/sources/bundle-config-ops.js +7 -2
- package/dist/commands/sources/info.js +18 -5
- package/dist/commands/sources/init.js +12 -12
- package/dist/commands/sources/installed-stashes.js +382 -98
- package/dist/commands/sources/schema-repair.js +3 -2
- package/dist/commands/sources/self-update.js +131 -38
- package/dist/commands/sources/source-add.js +72 -17
- package/dist/commands/sources/source-clone.js +129 -45
- package/dist/commands/sources/source-manage.js +43 -23
- package/dist/commands/sources/sources-cli.js +57 -208
- package/dist/commands/sources/stash-cli.js +46 -53
- package/dist/commands/tasks/tasks-cli.js +91 -97
- package/dist/commands/tasks/tasks.js +276 -421
- package/dist/commands/workflow-cli.js +175 -450
- package/dist/core/adapter/adapters/akm-adapter.js +47 -28
- package/dist/core/adapter/adapters/akm-lint.js +42 -27
- package/dist/core/adapter/adapters/akm-metadata.js +15 -44
- package/dist/core/adapter/adapters/akm-task-adapter.js +15 -13
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +55 -71
- package/dist/core/adapter/adapters/dotenv-adapter.js +1 -1
- package/dist/core/adapter/adapters/generic-files-adapter.js +2 -0
- package/dist/core/adapter/adapters/index.js +6 -6
- package/dist/core/adapter/adapters/llm-wiki-adapter.js +14 -8
- package/dist/core/adapter/adapters/okf-adapter.js +187 -19
- package/dist/core/adapter/adapters/shared.js +3 -19
- package/dist/core/adapter/adapters/tool-dir-shared.js +8 -3
- package/dist/core/adapter/adapters/website-snapshot-adapter.js +1 -0
- package/dist/core/adapter/detect-adapter.js +17 -0
- package/dist/core/adapter/recognize-match.js +6 -4
- package/dist/core/adapter/validate-context.js +214 -0
- package/dist/core/asset/akm-markdown.js +63 -0
- package/dist/core/asset/asset-placement.js +20 -6
- package/dist/core/asset/asset-ref.js +11 -9
- package/dist/core/asset/frontmatter-lint.js +30 -0
- package/dist/core/asset/frontmatter.js +37 -9
- package/dist/core/asset/markdown.js +40 -51
- package/dist/core/asset/resolve-ref.js +89 -18
- package/dist/core/asset/stash-meta.js +1 -1
- package/dist/core/bundle-id.js +51 -0
- package/dist/core/common.js +152 -38
- package/dist/core/config/config-io.js +12 -1
- package/dist/core/config/config-schema.js +35 -8
- package/dist/core/config/config-sources.js +55 -11
- package/dist/core/config/config-walker.js +25 -9
- package/dist/core/config/config.js +9 -48
- package/dist/core/config/experimental.js +21 -0
- package/dist/core/config/schema/embedding.js +5 -1
- package/dist/core/config/schema/experimental.js +30 -0
- package/dist/core/config/schema/improve-processes.js +0 -6
- package/dist/core/config/schema/improve.js +21 -3
- package/dist/core/config/schema/index-config.js +8 -15
- package/dist/core/config/schema/output.js +4 -1
- package/dist/core/config/schema/setup.js +9 -18
- package/dist/core/config/schema/sources-bundles.js +49 -33
- package/dist/core/config/schema/workflow.js +3 -3
- package/dist/core/env-secret-ref.js +76 -46
- package/dist/core/errors.js +18 -12
- package/dist/core/events.js +46 -128
- package/dist/core/file-change.js +6 -5
- package/dist/core/fs-txn.js +83 -7
- package/dist/core/git-message.js +2 -2
- package/dist/core/improve-result.js +1 -100
- package/dist/core/lesson-lint.js +1 -17
- package/dist/core/logs-db.js +2 -1
- package/dist/core/migration-operation.js +16 -0
- package/dist/core/mutation-target.js +78 -0
- package/dist/core/parse.js +4 -1
- package/dist/core/paths.js +17 -20
- package/dist/core/recognition-util.js +12 -14
- package/dist/core/redaction.js +34 -0
- package/dist/core/standards/resolve-standards-context.js +2 -14
- package/dist/core/standards/resolve-stash-standards.js +2 -2
- package/dist/core/standards/resolve-type-conventions.js +2 -2
- package/dist/core/state/migrations.js +41 -18
- package/dist/core/state-db.js +5 -14
- package/dist/core/structured.js +1 -1
- package/dist/core/subprocess.js +6 -4
- package/dist/core/text-truncation.js +9 -5
- package/dist/core/type-presentation.js +3 -3
- package/dist/core/warn.js +0 -3
- package/dist/core/write-source.js +771 -95
- package/dist/indexer/bundle-identity-guard.js +3 -2
- package/dist/indexer/db/graph-db.js +0 -24
- package/dist/indexer/ensure-index.js +1 -0
- package/dist/indexer/graph/graph-boost.js +9 -34
- package/dist/indexer/graph/graph-extraction.js +8 -5
- package/dist/indexer/index-writer-lock.js +53 -17
- package/dist/indexer/index-written-assets.js +16 -22
- package/dist/indexer/indexer.js +497 -239
- package/dist/indexer/installations.js +14 -96
- package/dist/indexer/passes/dir-staleness.js +16 -9
- package/dist/indexer/passes/memory-inference.js +11 -9
- package/dist/indexer/passes/metadata.js +113 -47
- package/dist/indexer/scan/doc-to-entry.js +38 -1
- package/dist/indexer/scan/drain-dir.js +13 -23
- package/dist/indexer/search/db-search.js +99 -54
- package/dist/indexer/search/fts-query.js +47 -24
- package/dist/indexer/search/ranking-contributors.js +42 -20
- package/dist/indexer/search/ranking.js +18 -99
- package/dist/indexer/search/search-fields.js +7 -2
- package/dist/indexer/search/search-source.js +82 -93
- package/dist/indexer/usage/usage-events.js +0 -89
- package/dist/indexer/walk/file-context.js +2 -1
- package/dist/indexer/walk/matchers.js +30 -43
- package/dist/indexer/walk/path-resolver.js +7 -2
- package/dist/indexer/walk/walker.js +38 -12
- package/dist/integrations/agent/builders.js +0 -6
- package/dist/integrations/agent/config.js +2 -2
- package/dist/integrations/agent/detect.js +49 -19
- package/dist/integrations/agent/engine-fallback.js +76 -0
- package/dist/integrations/agent/profiles.js +14 -0
- package/dist/integrations/agent/prompts.js +12 -8
- package/dist/integrations/agent/runner-dispatch.js +4 -2
- package/dist/integrations/agent/runner.js +0 -1
- package/dist/integrations/agent/spawn.js +5 -6
- package/dist/integrations/github.js +1 -1
- package/dist/integrations/harnesses/aider/agent-builder.js +6 -4
- package/dist/integrations/harnesses/amazonq/agent-builder.js +7 -4
- package/dist/integrations/harnesses/claude/session-log.js +0 -10
- package/dist/integrations/harnesses/codex/agent-builder.js +5 -2
- package/dist/integrations/harnesses/copilot/agent-builder.js +5 -3
- package/dist/integrations/harnesses/gemini/agent-builder.js +5 -3
- package/dist/integrations/harnesses/index.js +3 -7
- package/dist/integrations/harnesses/opencode/agent-builder.js +21 -2
- package/dist/integrations/harnesses/opencode/session-log.js +0 -15
- package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +13 -4
- package/dist/integrations/harnesses/openhands/agent-builder.js +9 -6
- package/dist/integrations/harnesses/pi/agent-builder.js +6 -4
- package/dist/integrations/lockfile.js +101 -6
- package/dist/integrations/session-logs/index.js +3 -28
- package/dist/llm/client.js +136 -100
- package/dist/llm/embedders/remote.js +13 -5
- package/dist/llm/feature-gate.js +4 -12
- package/dist/llm/graph-extract.js +5 -11
- package/dist/llm/memory-infer.js +144 -1
- package/dist/llm/metadata-enhance.js +5 -7
- package/dist/llm/structured-call.js +1 -1
- package/dist/llm/usage-persist.js +26 -5
- package/dist/llm/usage-telemetry.js +25 -2
- package/dist/output/cli-hints.js +1 -2
- package/dist/output/context.js +22 -7
- package/dist/output/format-exempt.js +80 -0
- package/dist/output/generic-render.js +259 -0
- package/dist/output/render-registry.js +57 -0
- package/dist/output/renderers.js +14 -36
- package/dist/output/shapes/curate.js +10 -1
- package/dist/output/shapes/events.js +12 -7
- package/dist/output/shapes/helpers.js +56 -83
- package/dist/output/shapes/migrate.js +8 -0
- package/dist/output/shapes/passthrough.js +7 -41
- package/dist/output/shapes/proposal/producer.js +15 -7
- package/dist/output/shapes.js +2 -9
- package/dist/output/text/{init.js → bundle-create.js} +3 -1
- package/dist/output/text/bundle-show.js +7 -0
- package/dist/output/text/command-format.js +164 -96
- package/dist/output/text/env.js +1 -3
- package/dist/output/text/events.js +8 -7
- package/dist/output/text/health-format.js +103 -0
- package/dist/output/text/health.js +7 -0
- package/dist/output/text/helpers.js +10 -8
- package/dist/output/text/lint-format.js +43 -0
- package/dist/output/text/{save.js → lint.js} +2 -2
- package/dist/output/text/migrate.js +88 -0
- package/dist/output/text/proposal/producer.js +4 -2
- package/dist/output/text/proposal-format.js +44 -72
- package/dist/output/text/registry-commands.js +1 -2
- package/dist/output/text/show-directives.js +15 -7
- package/dist/output/text/status-list.js +32 -0
- package/dist/output/text/sync.js +5 -0
- package/dist/output/text/workflow-format.js +24 -203
- package/dist/output/text/workflow.js +1 -7
- package/dist/output/text.js +16 -17
- package/dist/registry/factory.js +4 -6
- package/dist/registry/origin-resolve.js +16 -27
- package/dist/registry/providers/skills-sh.js +3 -3
- package/dist/registry/providers/static-index.js +13 -23
- package/dist/registry/resolve.js +42 -7
- package/dist/registry/semver.js +34 -84
- package/dist/runtime.js +2 -23
- package/dist/scripts/akm-migrate-node.js +60290 -0
- package/dist/scripts/akm-migrate.js +59628 -0
- package/dist/setup/detect.js +42 -15
- package/dist/setup/registry-stash-loader.js +2 -2
- package/dist/setup/setup.js +236 -136
- package/dist/setup/steps/connection.js +7 -9
- package/dist/setup/steps/platforms.js +9 -9
- package/dist/setup/steps/semantic.js +15 -3
- package/dist/setup/steps/sources.js +12 -13
- package/dist/setup/steps/stashdir.js +2 -3
- package/dist/setup/steps/tasks.js +237 -120
- package/dist/sources/freshness.js +1 -1
- package/dist/sources/provider-factory.js +11 -17
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +278 -34
- package/dist/sources/providers/git-provider.js +25 -23
- package/dist/sources/providers/git-stash.js +395 -106
- package/dist/sources/providers/git.js +2 -2
- package/dist/sources/providers/npm.js +16 -19
- package/dist/sources/providers/provider-utils.js +7 -4
- package/dist/sources/providers/sync-from-ref.js +3 -9
- package/dist/sources/providers/website.js +6 -1
- package/dist/sources/resolve.js +6 -5
- package/dist/sources/snapshot-fetchers/bluesky.js +146 -0
- package/dist/sources/snapshot-fetchers/content-extract.js +566 -0
- package/dist/sources/snapshot-fetchers/fetcher-util.js +41 -0
- package/dist/sources/snapshot-fetchers/github.js +100 -0
- package/dist/sources/snapshot-fetchers/host-guard.js +291 -0
- package/dist/sources/snapshot-fetchers/registry.js +17 -1
- package/dist/sources/snapshot-fetchers/robots.js +348 -0
- package/dist/sources/snapshot-fetchers/rss.js +282 -0
- package/dist/sources/snapshot-fetchers/secret-seam.js +42 -0
- package/dist/sources/snapshot-fetchers/website-ingest.js +566 -268
- package/dist/sources/snapshot-fetchers/x.js +910 -0
- package/dist/storage/database.js +7 -0
- package/dist/storage/engines/sqlite-migrations.js +23 -111
- package/dist/storage/managed-db.js +2 -2
- package/dist/storage/repositories/canaries-repository.js +1 -1
- package/dist/storage/repositories/events-repository.js +27 -11
- package/dist/storage/repositories/improve-runs-repository.js +6 -12
- package/dist/storage/repositories/index-connection.js +17 -6
- package/dist/storage/repositories/index-entries-repository.js +151 -240
- package/dist/storage/repositories/index-entry-mapper.js +15 -11
- package/dist/storage/repositories/index-fts-repository.js +5 -2
- package/dist/storage/repositories/index-llm-cache-repository.js +0 -1
- package/dist/storage/repositories/index-meta-repository.js +2 -3
- package/dist/storage/repositories/index-schema.js +10 -25
- package/dist/storage/repositories/index-utility-repository.js +15 -28
- package/dist/storage/repositories/index-vec-repository.js +6 -1
- package/dist/storage/repositories/outcome-repository.js +119 -0
- package/dist/storage/repositories/proposals-repository.js +296 -59
- package/dist/storage/repositories/registry-cache.js +19 -0
- package/dist/storage/repositories/salience-repository.js +172 -0
- package/dist/storage/repositories/task-history-repository.js +15 -13
- package/dist/storage/repositories/workflow-runs-repository.js +52 -40
- package/dist/tasks/backends/cron.js +105 -15
- package/dist/tasks/backends/index.js +1 -1
- package/dist/tasks/backends/launchd.js +85 -38
- package/dist/tasks/backends/schtasks.js +135 -15
- package/dist/tasks/embedded.js +56 -40
- package/dist/tasks/parser.js +7 -157
- package/dist/tasks/resolve-akm-bin.js +137 -59
- package/dist/tasks/runner.js +79 -42
- package/dist/tasks/scheduler-invocation.js +220 -10
- package/dist/tasks/schema.js +24 -1
- package/dist/tasks/task-id.js +1 -3
- package/dist/tasks/validator.js +20 -6
- package/dist/workflows/authoring/authoring.js +94 -143
- package/dist/workflows/authoring/scope-key.js +1 -1
- package/dist/workflows/exec/frozen-judge.js +28 -2
- package/dist/workflows/exec/native-executor.js +77 -57
- package/dist/workflows/exec/param-secrets.js +9 -9
- package/dist/workflows/exec/run-workflow.js +133 -79
- package/dist/workflows/exec/step-work.js +219 -346
- package/dist/{migrate-storage-node.mjs → workflows/exec/unit-dispatch.js} +1 -5
- package/dist/workflows/ir/compile.js +141 -270
- package/dist/workflows/ir/freeze.js +40 -30
- package/dist/workflows/ir/params.js +135 -11
- package/dist/workflows/ir/plan-hash.js +1 -1
- package/dist/workflows/ir/schema.js +25 -26
- package/dist/workflows/parser.js +872 -307
- package/dist/workflows/program/expressions.js +20 -208
- package/dist/workflows/program/schema.js +7 -10
- package/dist/workflows/renderer.js +95 -68
- package/dist/workflows/resource-limits.js +2 -0
- package/dist/workflows/runtime/checkin.js +3 -3
- package/dist/workflows/runtime/plan-classifier.js +16 -75
- package/dist/workflows/runtime/runs.js +186 -127
- package/dist/workflows/runtime/unit-checkin.js +1 -1
- package/dist/workflows/runtime/unit-phases.js +2 -2
- package/dist/workflows/runtime/workflow-asset-loader.js +232 -83
- package/dist/workflows/schema.js +1 -11
- package/dist/workflows/validate-summary.js +30 -36
- package/dist/workflows/validator.js +21 -62
- package/docs/README.md +68 -0
- package/docs/migration/README.md +8 -0
- package/docs/migration/release-notes/0.7.0.md +11 -11
- package/docs/migration/release-notes/0.9.0.md +208 -27
- package/docs/migration/v0.7-to-v0.8.md +46 -47
- package/docs/migration/v0.8-to-v0.9.md +564 -208
- package/docs/migration/v0.9.0-troubleshooting.md +561 -0
- package/docs/reference/README.md +12 -0
- package/docs/reference/cli.md +2253 -0
- package/docs/reference/configuration.md +358 -0
- package/docs/reference/data-and-telemetry.md +105 -42
- package/docs/reference/workflows.md +647 -0
- package/package.json +22 -11
- package/schemas/akm-asset-envelope.json +93 -0
- package/schemas/akm-config.json +81 -128
- package/schemas/akm-workflow.json +74 -73
- package/dist/assets/tasks/core/backup.yml +0 -5
- package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
- package/dist/cli/config-migrate.js +0 -1806
- package/dist/cli/config-validate.js +0 -41
- package/dist/commands/backup-cli.js +0 -56
- package/dist/commands/bundle/bundle-cli.js +0 -68
- package/dist/commands/bundle/bundle.js +0 -219
- package/dist/commands/graph/graph-cli.js +0 -124
- package/dist/commands/graph/graph.js +0 -489
- package/dist/commands/improve/extract-watch.js +0 -140
- package/dist/commands/mv-cli.js +0 -1221
- package/dist/commands/sources/history.js +0 -201
- package/dist/commands/tasks/default-tasks.js +0 -186
- package/dist/core/migration-backup.js +0 -1234
- package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -95
- package/dist/llm/memory-infer-impl.js +0 -138
- package/dist/migrate/legacy/config-source-migration.js +0 -223
- package/dist/migrate/legacy/content-migration.js +0 -305
- package/dist/migrate/legacy/legacy-layout.js +0 -779
- package/dist/migrate/legacy/legacy-paths.js +0 -25
- package/dist/migrate/legacy/legacy-stash-json.js +0 -72
- package/dist/migrate/legacy/proposal-fs-import.js +0 -168
- package/dist/migrate/legacy/task-target-ref-migration.js +0 -272
- package/dist/migrate/legacy/three-db-cutover.js +0 -841
- package/dist/migrate/legacy/workflow-migrations-bodies.js +0 -52
- package/dist/migrate/legacy/workflow-migrations-frozen.js +0 -21
- package/dist/migrate/legacy-ref-grammar.js +0 -214
- package/dist/output/shapes/distill.js +0 -14
- package/dist/output/shapes/history.js +0 -11
- package/dist/output/text/distill.js +0 -6
- package/dist/output/text/enable-disable.js +0 -8
- package/dist/output/text/history.js +0 -6
- package/dist/registry/build-index.js +0 -382
- package/dist/schemas/akm-config.json +0 -4704
- package/dist/schemas/akm-task.json +0 -87
- package/dist/schemas/akm-workflow.json +0 -372
- package/dist/scripts/migrate-storage.js +0 -3816
- package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
- package/dist/workflows/cli.js +0 -53
- package/dist/workflows/exec/brief.js +0 -481
- package/dist/workflows/exec/report.js +0 -1460
- package/dist/workflows/exec/watch.js +0 -116
- package/dist/workflows/program/parser.js +0 -813
- package/dist/workflows/program/project.js +0 -104
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { GITHUB_API_BASE, githubHeaders } from "../../integrations/github.js";
|
|
5
|
+
import { htmlToMarkdown } from "./content-extract.js";
|
|
6
|
+
import { avoidReservedBasename } from "./fetcher-util.js";
|
|
7
|
+
import { fetchPinnedText } from "./host-guard.js";
|
|
8
|
+
const GITHUB_HOSTS = new Set(["github.com", "www.github.com"]);
|
|
9
|
+
const RESERVED_GITHUB_ROOTS = new Set([
|
|
10
|
+
"apps",
|
|
11
|
+
"collections",
|
|
12
|
+
"codespaces",
|
|
13
|
+
"customer-stories",
|
|
14
|
+
"education",
|
|
15
|
+
"enterprise",
|
|
16
|
+
"events",
|
|
17
|
+
"explore",
|
|
18
|
+
"features",
|
|
19
|
+
"issues",
|
|
20
|
+
"login",
|
|
21
|
+
"marketplace",
|
|
22
|
+
"new",
|
|
23
|
+
"notifications",
|
|
24
|
+
"orgs",
|
|
25
|
+
"organizations",
|
|
26
|
+
"pricing",
|
|
27
|
+
"pulls",
|
|
28
|
+
"search",
|
|
29
|
+
"security",
|
|
30
|
+
"settings",
|
|
31
|
+
"solutions",
|
|
32
|
+
"sponsors",
|
|
33
|
+
"topics",
|
|
34
|
+
"trending",
|
|
35
|
+
"users",
|
|
36
|
+
]);
|
|
37
|
+
const GITHUB_README_BYTE_CAP = 5 * 1024 * 1024;
|
|
38
|
+
const GITHUB_README_BODY_TIMEOUT_MS = 30_000;
|
|
39
|
+
/** Match only repository roots; deeper GitHub pages stay generic web pages. */
|
|
40
|
+
export function extractGithubRepository(url) {
|
|
41
|
+
if (!GITHUB_HOSTS.has(url.hostname.toLowerCase()))
|
|
42
|
+
return null;
|
|
43
|
+
const segments = url.pathname.split("/").filter(Boolean);
|
|
44
|
+
if (segments.length !== 2)
|
|
45
|
+
return null;
|
|
46
|
+
let owner;
|
|
47
|
+
let repo;
|
|
48
|
+
try {
|
|
49
|
+
owner = decodeURIComponent(segments[0] ?? "");
|
|
50
|
+
repo = decodeURIComponent(segments[1] ?? "").replace(/\.git$/i, "");
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
if (!/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,38})$/.test(owner))
|
|
56
|
+
return null;
|
|
57
|
+
if (RESERVED_GITHUB_ROOTS.has(owner.toLowerCase()))
|
|
58
|
+
return null;
|
|
59
|
+
if (!/^[A-Za-z0-9._-]{1,100}$/.test(repo) || repo === "." || repo === "..")
|
|
60
|
+
return null;
|
|
61
|
+
return { owner, repo };
|
|
62
|
+
}
|
|
63
|
+
const githubFetcher = {
|
|
64
|
+
name: "github-repository",
|
|
65
|
+
matches(url) {
|
|
66
|
+
return extractGithubRepository(url) !== null;
|
|
67
|
+
},
|
|
68
|
+
async fetch(url, context) {
|
|
69
|
+
const repository = extractGithubRepository(url);
|
|
70
|
+
if (!repository)
|
|
71
|
+
return null;
|
|
72
|
+
const apiUrl = `${GITHUB_API_BASE}/repos/${encodeURIComponent(repository.owner)}/${encodeURIComponent(repository.repo)}/readme`;
|
|
73
|
+
const headers = new Headers(githubHeaders(apiUrl));
|
|
74
|
+
headers.set("Accept", "application/vnd.github.html+json");
|
|
75
|
+
headers.set("User-Agent", "akm-cli github snapshot fetcher");
|
|
76
|
+
const html = await fetchPinnedText(apiUrl, {
|
|
77
|
+
headers,
|
|
78
|
+
byteCap: GITHUB_README_BYTE_CAP,
|
|
79
|
+
bodyTimeoutMs: GITHUB_README_BODY_TIMEOUT_MS,
|
|
80
|
+
timeoutMs: context.timeoutMs,
|
|
81
|
+
signal: context.signal,
|
|
82
|
+
});
|
|
83
|
+
if (!html)
|
|
84
|
+
return null;
|
|
85
|
+
const canonicalUrl = `https://github.com/${repository.owner}/${repository.repo}`;
|
|
86
|
+
const markdown = htmlToMarkdown(html, `${canonicalUrl}/`);
|
|
87
|
+
if (!markdown)
|
|
88
|
+
return null;
|
|
89
|
+
const repoName = repository.repo.startsWith(".") ? `dot-${repository.repo.slice(1)}` : repository.repo;
|
|
90
|
+
const preferredName = `${repository.owner.toLowerCase()}/${repoName.toLowerCase()}`;
|
|
91
|
+
return {
|
|
92
|
+
url: canonicalUrl,
|
|
93
|
+
title: `${repository.owner}/${repository.repo}`,
|
|
94
|
+
markdown,
|
|
95
|
+
preferredName: avoidReservedBasename(preferredName),
|
|
96
|
+
tags: ["github", "repository", repository.owner.toLowerCase()],
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
export default githubFetcher;
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { lookup as dnsLookup } from "node:dns/promises";
|
|
5
|
+
import { isIP } from "node:net";
|
|
6
|
+
import { fetchWithRetry, readBodyWithByteCap } from "../../core/common.js";
|
|
7
|
+
/** Redirect budget for a single guarded request chain. */
|
|
8
|
+
const MAX_GUARDED_REDIRECTS = 8;
|
|
9
|
+
export function assertWebsiteRequestUrl(rawUrl, ErrorType = Error, options) {
|
|
10
|
+
const parsedUrl = new URL(rawUrl);
|
|
11
|
+
if (parsedUrl.username || parsedUrl.password) {
|
|
12
|
+
throw new ErrorType(`Refusing to fetch URL with embedded credentials: ${parsedUrl.hostname}`);
|
|
13
|
+
}
|
|
14
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
15
|
+
throw new ErrorType(`Refusing to fetch non-http(s) URL on host: ${parsedUrl.hostname}`);
|
|
16
|
+
}
|
|
17
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
18
|
+
if (hostname.endsWith(".invalid")) {
|
|
19
|
+
throw new ErrorType(`Refusing to fetch reserved invalid hostname: ${parsedUrl.hostname}`);
|
|
20
|
+
}
|
|
21
|
+
if (isForbiddenWebsiteHostname(hostname, options)) {
|
|
22
|
+
throw new ErrorType(`Refusing to fetch non-public website host: ${parsedUrl.hostname}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async function defaultResolveHostname(hostname) {
|
|
26
|
+
const records = await dnsLookup(hostname, { all: true });
|
|
27
|
+
return records.map((record) => record.address);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Resolve-then-validate SSRF guard against DNS rebinding / private-range
|
|
31
|
+
* bypasses. {@link assertWebsiteRequestUrl} only rejects IP-literal and
|
|
32
|
+
* well-known-name hosts; a hostname like `private-host.example.com` that
|
|
33
|
+
* resolves to `10.0.0.1` passes those checks and then fetch connects to the
|
|
34
|
+
* private address. Here we resolve EVERY A/AAAA record and validate each
|
|
35
|
+
* against the same forbidden-range rules, failing CLOSED on an empty answer or
|
|
36
|
+
* resolver error.
|
|
37
|
+
*
|
|
38
|
+
* TOCTOU residual (documented, not fully closable here): Bun's `fetch` exposes
|
|
39
|
+
* no custom `lookup`/agent hook, so we cannot pin the socket to the exact IP we
|
|
40
|
+
* validated — a hostile resolver could return a public IP to this lookup and a
|
|
41
|
+
* private IP microseconds later at connect time (classic rebinding). This still
|
|
42
|
+
* removes the TRIVIAL `hostname A 10.0.0.1` bypass, which is the strongest
|
|
43
|
+
* guarantee available without a pinned-connection fetch API. Re-run on every
|
|
44
|
+
* redirect hop (the crawler recurses through `fetchWebsiteResponse`).
|
|
45
|
+
*/
|
|
46
|
+
export async function assertResolvedHostAllowed(hostname, options) {
|
|
47
|
+
if (options?.allowPrivateHosts === true)
|
|
48
|
+
return;
|
|
49
|
+
const bare = stripIpv6Brackets(hostname.toLowerCase());
|
|
50
|
+
const literalVersion = isIP(bare);
|
|
51
|
+
if (literalVersion !== 0) {
|
|
52
|
+
const forbidden = literalVersion === 4 ? isForbiddenIpv4(bare) : isForbiddenIpv6(bare);
|
|
53
|
+
if (forbidden)
|
|
54
|
+
throw new Error(`Refusing to fetch non-public website host: ${hostname}`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const resolve = options?.resolveHostname ?? defaultResolveHostname;
|
|
58
|
+
let addresses;
|
|
59
|
+
try {
|
|
60
|
+
addresses = await resolve(bare);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error(`Refusing to fetch ${hostname}: DNS resolution failed`);
|
|
64
|
+
}
|
|
65
|
+
if (addresses.length === 0) {
|
|
66
|
+
throw new Error(`Refusing to fetch ${hostname}: hostname resolved to no addresses`);
|
|
67
|
+
}
|
|
68
|
+
for (const address of addresses) {
|
|
69
|
+
const version = isIP(address);
|
|
70
|
+
const forbidden = version === 4 ? isForbiddenIpv4(address) : version === 6 ? isForbiddenIpv6(stripIpv6Brackets(address)) : true;
|
|
71
|
+
if (forbidden) {
|
|
72
|
+
throw new Error(`Refusing to fetch ${hostname}: resolves to non-public or unparseable address ${address}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// WHATWG URL.hostname wraps IPv6 literals in brackets (e.g. "[::1]"), but
|
|
77
|
+
// node:net's isIP() only recognizes the bare address form and returns 0 for
|
|
78
|
+
// anything bracketed — silently skipping all IPv6 forbidden-host checks
|
|
79
|
+
// below for every hostname parsed off a URL. Strip the brackets before any
|
|
80
|
+
// isIP()/isForbiddenIpv6() call so those checks actually run.
|
|
81
|
+
function stripIpv6Brackets(hostname) {
|
|
82
|
+
return hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname;
|
|
83
|
+
}
|
|
84
|
+
function isForbiddenWebsiteHostname(hostname, options) {
|
|
85
|
+
if (options?.allowPrivateHosts === true)
|
|
86
|
+
return false;
|
|
87
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname === "metadata.google.internal") {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
const bareHostname = stripIpv6Brackets(hostname);
|
|
91
|
+
const ipVersion = isIP(bareHostname);
|
|
92
|
+
if (ipVersion === 4)
|
|
93
|
+
return isForbiddenIpv4(bareHostname);
|
|
94
|
+
if (ipVersion === 6)
|
|
95
|
+
return isForbiddenIpv6(bareHostname);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
export function isLoopbackWebsiteHostname(hostname) {
|
|
99
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost"))
|
|
100
|
+
return true;
|
|
101
|
+
const bareHostname = stripIpv6Brackets(hostname);
|
|
102
|
+
const ipVersion = isIP(bareHostname);
|
|
103
|
+
if (ipVersion === 4)
|
|
104
|
+
return bareHostname.startsWith("127.");
|
|
105
|
+
if (ipVersion === 6)
|
|
106
|
+
return bareHostname === "::1";
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
function isForbiddenIpv4(hostname) {
|
|
110
|
+
const parts = hostname.split(".").map((part) => Number.parseInt(part, 10));
|
|
111
|
+
const [a = -1, b = -1, c = -1, d = -1] = parts;
|
|
112
|
+
if (parts.length !== 4 || [a, b, c, d].some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
return (a === 0 ||
|
|
116
|
+
a === 10 ||
|
|
117
|
+
(a === 100 && b >= 64 && b <= 127) ||
|
|
118
|
+
a === 127 ||
|
|
119
|
+
(a === 169 && b === 254) ||
|
|
120
|
+
(a === 172 && b >= 16 && b <= 31) ||
|
|
121
|
+
(a === 192 && b === 0 && c === 0 && d !== 9 && d !== 10) ||
|
|
122
|
+
(a === 192 && b === 0 && c === 2) ||
|
|
123
|
+
(a === 192 && b === 168) ||
|
|
124
|
+
(a === 192 && b === 88 && c === 99) ||
|
|
125
|
+
(a === 198 && (b === 18 || b === 19)) ||
|
|
126
|
+
(a === 198 && b === 51 && c === 100) ||
|
|
127
|
+
(a === 203 && b === 0 && c === 113) ||
|
|
128
|
+
a >= 224);
|
|
129
|
+
}
|
|
130
|
+
function parseIpv6Words(hostname) {
|
|
131
|
+
let normalized;
|
|
132
|
+
try {
|
|
133
|
+
normalized = stripIpv6Brackets(new URL(`http://[${stripIpv6Brackets(hostname)}]/`).hostname).toLowerCase();
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const halves = normalized.split("::");
|
|
139
|
+
if (halves.length > 2)
|
|
140
|
+
return null;
|
|
141
|
+
const left = halves[0] ? halves[0].split(":") : [];
|
|
142
|
+
const right = halves[1] ? halves[1].split(":") : [];
|
|
143
|
+
const missing = 8 - left.length - right.length;
|
|
144
|
+
if ((halves.length === 1 && missing !== 0) || (halves.length === 2 && missing < 1))
|
|
145
|
+
return null;
|
|
146
|
+
const parts = halves.length === 2 ? [...left, ...Array(missing).fill("0"), ...right] : left;
|
|
147
|
+
if (parts.length !== 8 || parts.some((part) => !/^[0-9a-f]{1,4}$/.test(part)))
|
|
148
|
+
return null;
|
|
149
|
+
return parts.map((part) => Number.parseInt(part, 16));
|
|
150
|
+
}
|
|
151
|
+
function ipv6Value(hostname) {
|
|
152
|
+
const words = parseIpv6Words(hostname);
|
|
153
|
+
if (!words)
|
|
154
|
+
return null;
|
|
155
|
+
return words.reduce((value, word) => (value << 16n) | BigInt(word), 0n);
|
|
156
|
+
}
|
|
157
|
+
const FORBIDDEN_IPV6_RANGES = [
|
|
158
|
+
["64:ff9b:1::", 48],
|
|
159
|
+
["100::", 64],
|
|
160
|
+
["2001::", 32],
|
|
161
|
+
["2001:2::", 48],
|
|
162
|
+
["2001:10::", 28],
|
|
163
|
+
["2001:20::", 28],
|
|
164
|
+
["2001:db8::", 32],
|
|
165
|
+
["3fff::", 20],
|
|
166
|
+
["5f00::", 16],
|
|
167
|
+
["fc00::", 7],
|
|
168
|
+
["fe80::", 10],
|
|
169
|
+
["fec0::", 10],
|
|
170
|
+
["ff00::", 8],
|
|
171
|
+
];
|
|
172
|
+
function embeddedIpv4Address(words) {
|
|
173
|
+
const firstSixZero = words.slice(0, 6).every((word) => word === 0);
|
|
174
|
+
const mapped = words.slice(0, 5).every((word) => word === 0) && words[5] === 0xffff;
|
|
175
|
+
const translated = words.slice(0, 4).every((word) => word === 0) && words[4] === 0xffff && words[5] === 0;
|
|
176
|
+
const wellKnownNat64 = words[0] === 0x64 && words[1] === 0xff9b && words.slice(2, 6).every((word) => word === 0);
|
|
177
|
+
if (words[0] === 0x2002)
|
|
178
|
+
return ipv4FromHextets(words[1] ?? 0, words[2] ?? 0);
|
|
179
|
+
if (!firstSixZero && !mapped && !translated && !wellKnownNat64)
|
|
180
|
+
return null;
|
|
181
|
+
return ipv4FromHextets(words[6] ?? 0, words[7] ?? 0);
|
|
182
|
+
}
|
|
183
|
+
function ipv4FromHextets(high, low) {
|
|
184
|
+
return `${(high >> 8) & 0xff}.${high & 0xff}.${(low >> 8) & 0xff}.${low & 0xff}`;
|
|
185
|
+
}
|
|
186
|
+
function isForbiddenIpv6(hostname) {
|
|
187
|
+
const words = parseIpv6Words(hostname);
|
|
188
|
+
const value = ipv6Value(hostname);
|
|
189
|
+
if (!words || value === null)
|
|
190
|
+
return true;
|
|
191
|
+
const embeddedIpv4 = embeddedIpv4Address(words);
|
|
192
|
+
if (embeddedIpv4)
|
|
193
|
+
return isForbiddenIpv4(embeddedIpv4);
|
|
194
|
+
return FORBIDDEN_IPV6_RANGES.some(([prefix, bits]) => {
|
|
195
|
+
const prefixValue = ipv6Value(prefix);
|
|
196
|
+
if (prefixValue === null)
|
|
197
|
+
return true;
|
|
198
|
+
const shift = BigInt(128 - bits);
|
|
199
|
+
return value >> shift === prefixValue >> shift;
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
function redirectInit(init, currentUrl, nextUrl) {
|
|
203
|
+
if (new URL(currentUrl).origin === new URL(nextUrl).origin)
|
|
204
|
+
return init;
|
|
205
|
+
const headers = new Headers(init.headers);
|
|
206
|
+
headers.delete("authorization");
|
|
207
|
+
headers.delete("cookie");
|
|
208
|
+
headers.delete("proxy-authorization");
|
|
209
|
+
return { ...init, headers };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Fetch with the full SSRF guard chain applied to the initial URL and to every
|
|
213
|
+
* redirect hop.
|
|
214
|
+
*
|
|
215
|
+
* `redirect: "manual"` is essential: with the default `follow`, the platform
|
|
216
|
+
* resolves and connects to redirect targets that were never validated, so a
|
|
217
|
+
* public host can bounce a request into the private network.
|
|
218
|
+
*/
|
|
219
|
+
export async function fetchGuardedResponse(url, init, opts, redirectCount = 0) {
|
|
220
|
+
assertWebsiteRequestUrl(url, Error, opts);
|
|
221
|
+
await assertResolvedHostAllowed(new URL(url).hostname, opts);
|
|
222
|
+
const response = await fetchWithRetry(url, { ...init, redirect: "manual" }, {
|
|
223
|
+
timeout: opts.timeoutMs,
|
|
224
|
+
retries: opts.retries ?? 1,
|
|
225
|
+
});
|
|
226
|
+
if (response.status >= 300 && response.status < 400) {
|
|
227
|
+
if (redirectCount >= MAX_GUARDED_REDIRECTS) {
|
|
228
|
+
await response.body?.cancel().catch(() => undefined);
|
|
229
|
+
throw new Error(`Too many redirects while fetching ${url}`);
|
|
230
|
+
}
|
|
231
|
+
const location = response.headers.get("location");
|
|
232
|
+
if (!location) {
|
|
233
|
+
await response.body?.cancel().catch(() => undefined);
|
|
234
|
+
throw new Error(`Redirect response from ${url} did not include a Location header`);
|
|
235
|
+
}
|
|
236
|
+
await response.body?.cancel().catch(() => undefined);
|
|
237
|
+
const nextUrl = new URL(location, url).toString();
|
|
238
|
+
return fetchGuardedResponse(nextUrl, redirectInit(init, url, nextUrl), opts, redirectCount + 1);
|
|
239
|
+
}
|
|
240
|
+
return { response, finalUrl: url };
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* GET text from a compile-time fixed API host, rejecting redirects so request
|
|
244
|
+
* headers (which may contain a token) can never be forwarded elsewhere.
|
|
245
|
+
*/
|
|
246
|
+
export async function fetchPinnedText(url, options) {
|
|
247
|
+
const response = await fetchWithRetry(url, { headers: options.headers, signal: options.signal, redirect: "manual" }, { timeout: options.timeoutMs, retries: options.retries ?? 1 });
|
|
248
|
+
if (response.status >= 300 && response.status < 400) {
|
|
249
|
+
await response.body?.cancel().catch(() => undefined);
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
if (!response.ok) {
|
|
253
|
+
await response.body?.cancel().catch(() => undefined);
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
return await readBodyWithByteCap(response, options.byteCap, {
|
|
258
|
+
bodyTimeoutMs: options.bodyTimeoutMs,
|
|
259
|
+
signal: options.signal,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
if (options.signal?.aborted)
|
|
264
|
+
throw error;
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* GET a JSON document from a PINNED, constant API host (X, Bluesky XRPC), with
|
|
270
|
+
* a byte-capped read. Returns the parsed value, or null on any 3xx/non-2xx/
|
|
271
|
+
* parse failure.
|
|
272
|
+
*
|
|
273
|
+
* `redirect: "manual"` + rejecting 3xx is load-bearing: these endpoints have no
|
|
274
|
+
* legitimate reason to redirect, and following one would send the next request
|
|
275
|
+
* (bearer token included) to an unvalidated host with none of the SSRF guards
|
|
276
|
+
* the crawl path applies. The full resolve-then-validate DNS guard is
|
|
277
|
+
* intentionally NOT applied here because the host is a compile-time constant,
|
|
278
|
+
* not caller-supplied — the guarded chokepoint is {@link fetchGuardedResponse},
|
|
279
|
+
* which the caller-supplied crawl/feed paths use instead.
|
|
280
|
+
*/
|
|
281
|
+
export async function fetchPinnedJson(url, options) {
|
|
282
|
+
const body = await fetchPinnedText(url, options);
|
|
283
|
+
if (body === null)
|
|
284
|
+
return null;
|
|
285
|
+
try {
|
|
286
|
+
return JSON.parse(body);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
@@ -5,10 +5,26 @@ import fs from "node:fs";
|
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import { warn } from "../../core/warn.js";
|
|
8
|
+
import blueskyFetcher from "./bluesky.js";
|
|
9
|
+
import githubFetcher from "./github.js";
|
|
10
|
+
import rssFetcher from "./rss.js";
|
|
11
|
+
import xFetcher from "./x.js";
|
|
8
12
|
import youtubeFetcher from "./youtube.js";
|
|
9
13
|
const FETCHER_DIR = path.join("scripts", "wiki-fetchers");
|
|
10
14
|
const FETCHER_FILE_PATTERN = /\.(?:ts|js|mjs)$/i;
|
|
11
|
-
|
|
15
|
+
/**
|
|
16
|
+
* Order matters: the first fetcher whose `matches` returns true and whose
|
|
17
|
+
* `fetch` returns non-null wins. Host-specific fetchers therefore precede the
|
|
18
|
+
* generic RSS fetcher, whose `matches` is path-shaped and deliberately loose.
|
|
19
|
+
* Any fetcher returning null falls through to the generic website crawler.
|
|
20
|
+
*/
|
|
21
|
+
const BUILTIN_FETCHERS = [
|
|
22
|
+
githubFetcher,
|
|
23
|
+
youtubeFetcher,
|
|
24
|
+
blueskyFetcher,
|
|
25
|
+
xFetcher,
|
|
26
|
+
rssFetcher,
|
|
27
|
+
];
|
|
12
28
|
function isWikiSnapshotFetcher(value) {
|
|
13
29
|
if (!value || typeof value !== "object")
|
|
14
30
|
return false;
|