@warpgogol/forge 5.2.3 → 5.3.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/AGENTS.md +34 -2
- package/README.md +1 -1
- package/README.uk.md +8 -8
- package/bin/cli.ts +1 -0
- package/dist/bin/cli.js +1 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/os/adr/adr.module.d.ts.map +1 -1
- package/dist/os/adr/adr.module.js +8 -3
- package/dist/os/adr/adr.module.js.map +1 -1
- package/dist/os/adr/frontmatter-io.js +5 -5
- package/dist/os/adr/frontmatter-io.js.map +1 -1
- package/dist/os/adr/handlers/archive.d.ts.map +1 -1
- package/dist/os/adr/handlers/archive.js +10 -4
- package/dist/os/adr/handlers/archive.js.map +1 -1
- package/dist/os/adr/handlers/implement-stamp.d.ts.map +1 -1
- package/dist/os/adr/handlers/implement-stamp.js +10 -9
- package/dist/os/adr/handlers/implement-stamp.js.map +1 -1
- package/dist/os/adr/handlers/list-create.js +5 -5
- package/dist/os/adr/handlers/list-create.js.map +1 -1
- package/dist/os/adr/handlers/validate.js +1 -1
- package/dist/os/adr/handlers/validate.js.map +1 -1
- package/dist/os/audit/audit.module.js +3 -3
- package/dist/os/audit/audit.module.js.map +1 -1
- package/dist/os/audit/frontmatter-io.js +5 -5
- package/dist/os/audit/frontmatter-io.js.map +1 -1
- package/dist/os/audit/handlers/archive.d.ts.map +1 -1
- package/dist/os/audit/handlers/archive.js +10 -4
- package/dist/os/audit/handlers/archive.js.map +1 -1
- package/dist/os/compass/compass.module.d.ts.map +1 -1
- package/dist/os/compass/compass.module.js +17 -1
- package/dist/os/compass/compass.module.js.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.js +12 -5
- package/dist/os/compass/handlers/compass-audit-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-change-summary-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-change-summary-handler.js +3 -2
- package/dist/os/compass/handlers/compass-change-summary-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-inventory-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-inventory-handler.js +6 -4
- package/dist/os/compass/handlers/compass-inventory-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-inventory.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-inventory.js +10 -8
- package/dist/os/compass/handlers/compass-inventory.js.map +1 -1
- package/dist/os/compass/handlers/compass-migrate-handler.js +1 -1
- package/dist/os/compass/handlers/compass-migrate-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-migrate.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-migrate.js +6 -3
- package/dist/os/compass/handlers/compass-migrate.js.map +1 -1
- package/dist/os/compass/handlers/git-revision.d.ts.map +1 -1
- package/dist/os/compass/handlers/git-revision.js +9 -6
- package/dist/os/compass/handlers/git-revision.js.map +1 -1
- package/dist/os/compass/handlers/resolve-scan-root.js +1 -1
- package/dist/os/compass/handlers/resolve-scan-root.js.map +1 -1
- package/dist/os/compass/handlers/summary-record.d.ts.map +1 -1
- package/dist/os/compass/handlers/summary-record.js +8 -4
- package/dist/os/compass/handlers/summary-record.js.map +1 -1
- package/dist/os/core/core.module.d.ts.map +1 -1
- package/dist/os/core/core.module.js +57 -4
- package/dist/os/core/core.module.js.map +1 -1
- package/dist/os/core/handlers/assets-helpers.d.ts.map +1 -1
- package/dist/os/core/handlers/assets-helpers.js +6 -3
- package/dist/os/core/handlers/assets-helpers.js.map +1 -1
- package/dist/os/core/handlers/build.js +1 -1
- package/dist/os/core/handlers/build.js.map +1 -1
- package/dist/os/core/handlers/determinism-check.d.ts.map +1 -1
- package/dist/os/core/handlers/determinism-check.js +10 -6
- package/dist/os/core/handlers/determinism-check.js.map +1 -1
- package/dist/os/core/handlers/dev.js +1 -1
- package/dist/os/core/handlers/dev.js.map +1 -1
- package/dist/os/core/handlers/file-size-lint.d.ts.map +1 -1
- package/dist/os/core/handlers/file-size-lint.js +11 -7
- package/dist/os/core/handlers/file-size-lint.js.map +1 -1
- package/dist/os/core/handlers/forge-autonomy-validate.d.ts.map +1 -1
- package/dist/os/core/handlers/forge-autonomy-validate.js +9 -5
- package/dist/os/core/handlers/forge-autonomy-validate.js.map +1 -1
- package/dist/os/core/handlers/knowledge-compact.js +1 -1
- package/dist/os/core/handlers/knowledge-compact.js.map +1 -1
- package/dist/os/core/handlers/package-health.js +1 -1
- package/dist/os/core/handlers/package-health.js.map +1 -1
- package/dist/os/core/handlers/pinned-check.js +2 -2
- package/dist/os/core/handlers/pinned-check.js.map +1 -1
- package/dist/os/core/handlers/pinned-init.d.ts.map +1 -1
- package/dist/os/core/handlers/pinned-init.js +9 -8
- package/dist/os/core/handlers/pinned-init.js.map +1 -1
- package/dist/os/core/handlers/pinned-validate.js +3 -3
- package/dist/os/core/handlers/pinned-validate.js.map +1 -1
- package/dist/os/core/handlers/profile-resolve.js +1 -1
- package/dist/os/core/handlers/profile-resolve.js.map +1 -1
- package/dist/os/core/handlers/public-surface.js +1 -1
- package/dist/os/core/handlers/public-surface.js.map +1 -1
- package/dist/os/core/handlers/release-prepare.d.ts.map +1 -1
- package/dist/os/core/handlers/release-prepare.js +10 -6
- package/dist/os/core/handlers/release-prepare.js.map +1 -1
- package/dist/os/core/handlers/release-publish.js +6 -6
- package/dist/os/core/handlers/release-publish.js.map +1 -1
- package/dist/os/core/handlers/validate.js +1 -1
- package/dist/os/core/handlers/validate.js.map +1 -1
- package/dist/os/exploration/exploration.module.d.ts.map +1 -1
- package/dist/os/exploration/exploration.module.js +1 -0
- package/dist/os/exploration/exploration.module.js.map +1 -1
- package/dist/os/exploration/frontmatter-io.js +4 -4
- package/dist/os/exploration/frontmatter-io.js.map +1 -1
- package/dist/os/exploration/handlers/archive.js +3 -3
- package/dist/os/exploration/handlers/archive.js.map +1 -1
- package/dist/os/mission/handlers/archive.d.ts.map +1 -1
- package/dist/os/mission/handlers/archive.js +22 -19
- package/dist/os/mission/handlers/archive.js.map +1 -1
- package/dist/os/mission/mission.module.js +3 -3
- package/dist/os/mission/mission.module.js.map +1 -1
- package/dist/os/naming/naming-convention.d.ts.map +1 -1
- package/dist/os/naming/naming-convention.js +7 -3
- package/dist/os/naming/naming-convention.js.map +1 -1
- package/dist/os/naming/naming.module.d.ts.map +1 -1
- package/dist/os/naming/naming.module.js +4 -3
- package/dist/os/naming/naming.module.js.map +1 -1
- package/dist/os/notes/notes.module.d.ts.map +1 -1
- package/dist/os/notes/notes.module.js +6 -3
- package/dist/os/notes/notes.module.js.map +1 -1
- package/dist/os/plan/frontmatter-io.js +5 -5
- package/dist/os/plan/frontmatter-io.js.map +1 -1
- package/dist/os/plan/handlers/archive.d.ts.map +1 -1
- package/dist/os/plan/handlers/archive.js +10 -4
- package/dist/os/plan/handlers/archive.js.map +1 -1
- package/dist/os/plan/plan.module.js +3 -3
- package/dist/os/plan/plan.module.js.map +1 -1
- package/dist/os/plugin/plugin.module.d.ts.map +1 -1
- package/dist/os/plugin/plugin.module.js +6 -3
- package/dist/os/plugin/plugin.module.js.map +1 -1
- package/dist/os/program/discovery.js +2 -2
- package/dist/os/program/discovery.js.map +1 -1
- package/dist/os/program/handlers/complete.js +1 -1
- package/dist/os/program/handlers/complete.js.map +1 -1
- package/dist/os/program/handlers/lease.js +1 -1
- package/dist/os/program/handlers/lease.js.map +1 -1
- package/dist/os/program/handlers/seal.js +1 -1
- package/dist/os/program/handlers/seal.js.map +1 -1
- package/dist/os/program/lease.js +1 -1
- package/dist/os/program/lease.js.map +1 -1
- package/dist/os/program/program.module.d.ts.map +1 -1
- package/dist/os/program/program.module.js +9 -3
- package/dist/os/program/program.module.js.map +1 -1
- package/dist/os/queue/index.js +1 -1
- package/dist/os/queue/manifest.d.ts.map +1 -1
- package/dist/os/queue/manifest.js +4 -8
- package/dist/os/queue/manifest.js.map +1 -1
- package/dist/os/rfc/acceptance.d.ts.map +1 -1
- package/dist/os/rfc/acceptance.js +10 -6
- package/dist/os/rfc/acceptance.js.map +1 -1
- package/dist/os/rfc/decision-log.d.ts.map +1 -1
- package/dist/os/rfc/decision-log.js +4 -3
- package/dist/os/rfc/decision-log.js.map +1 -1
- package/dist/os/rfc/dna-trace.d.ts.map +1 -1
- package/dist/os/rfc/dna-trace.js +7 -2
- package/dist/os/rfc/dna-trace.js.map +1 -1
- package/dist/os/rfc/frontmatter-io.js +5 -5
- package/dist/os/rfc/frontmatter-io.js.map +1 -1
- package/dist/os/rfc/handlers/archive.d.ts.map +1 -1
- package/dist/os/rfc/handlers/archive.js +11 -5
- package/dist/os/rfc/handlers/archive.js.map +1 -1
- package/dist/os/rfc/handlers/check.d.ts.map +1 -1
- package/dist/os/rfc/handlers/check.js +6 -3
- package/dist/os/rfc/handlers/check.js.map +1 -1
- package/dist/os/rfc/handlers/implement-stamp.d.ts.map +1 -1
- package/dist/os/rfc/handlers/implement-stamp.js +11 -10
- package/dist/os/rfc/handlers/implement-stamp.js.map +1 -1
- package/dist/os/rfc/handlers/index-graph.js +3 -3
- package/dist/os/rfc/handlers/index-graph.js.map +1 -1
- package/dist/os/rfc/handlers/lifecycle.d.ts.map +1 -1
- package/dist/os/rfc/handlers/lifecycle.js +6 -2
- package/dist/os/rfc/handlers/lifecycle.js.map +1 -1
- package/dist/os/rfc/handlers/list-create.d.ts.map +1 -1
- package/dist/os/rfc/handlers/list-create.js +7 -6
- package/dist/os/rfc/handlers/list-create.js.map +1 -1
- package/dist/os/rfc/handlers/shared.js +3 -3
- package/dist/os/rfc/handlers/shared.js.map +1 -1
- package/dist/os/rfc/handlers/supersede-propose.d.ts.map +1 -1
- package/dist/os/rfc/handlers/supersede-propose.js +7 -3
- package/dist/os/rfc/handlers/supersede-propose.js.map +1 -1
- package/dist/os/rfc/handlers/validate-rules.d.ts.map +1 -1
- package/dist/os/rfc/handlers/validate-rules.js +8 -5
- package/dist/os/rfc/handlers/validate-rules.js.map +1 -1
- package/dist/os/rfc/rfc.module.d.ts.map +1 -1
- package/dist/os/rfc/rfc.module.js +44 -4
- package/dist/os/rfc/rfc.module.js.map +1 -1
- package/dist/os/rfc/verification-evidence.d.ts.map +1 -1
- package/dist/os/rfc/verification-evidence.js +9 -5
- package/dist/os/rfc/verification-evidence.js.map +1 -1
- package/dist/os/rfc/verification-refresh.d.ts.map +1 -1
- package/dist/os/rfc/verification-refresh.js +4 -3
- package/dist/os/rfc/verification-refresh.js.map +1 -1
- package/dist/os/session/frontmatter-io.js +12 -12
- package/dist/os/session/frontmatter-io.js.map +1 -1
- package/dist/os/session/handlers/archive.d.ts.map +1 -1
- package/dist/os/session/handlers/archive.js +10 -4
- package/dist/os/session/handlers/archive.js.map +1 -1
- package/dist/os/session/handlers/metrics-aggregate.d.ts.map +1 -1
- package/dist/os/session/handlers/metrics-aggregate.js +9 -5
- package/dist/os/session/handlers/metrics-aggregate.js.map +1 -1
- package/dist/os/session/handlers/metrics-rfc.d.ts.map +1 -1
- package/dist/os/session/handlers/metrics-rfc.js +11 -8
- package/dist/os/session/handlers/metrics-rfc.js.map +1 -1
- package/dist/os/session/handlers/metrics-session.d.ts.map +1 -1
- package/dist/os/session/handlers/metrics-session.js +6 -3
- package/dist/os/session/handlers/metrics-session.js.map +1 -1
- package/dist/os/session/handlers/save.d.ts.map +1 -1
- package/dist/os/session/handlers/save.js +14 -8
- package/dist/os/session/handlers/save.js.map +1 -1
- package/dist/os/session/session.module.d.ts.map +1 -1
- package/dist/os/session/session.module.js +6 -0
- package/dist/os/session/session.module.js.map +1 -1
- package/dist/os/spec/live-spec-list.js +4 -4
- package/dist/os/spec/live-spec-list.js.map +1 -1
- package/dist/os/spec/live-spec-merge.js +5 -5
- package/dist/os/spec/live-spec-merge.js.map +1 -1
- package/dist/os/spec/live-spec-show.js +3 -3
- package/dist/os/spec/live-spec-show.js.map +1 -1
- package/dist/os/spec/live-spec-validate.js +4 -4
- package/dist/os/spec/live-spec-validate.js.map +1 -1
- package/dist/os/spec/spec-materialize.js +9 -9
- package/dist/os/spec/spec-materialize.js.map +1 -1
- package/dist/os/spec/spec-status.js +9 -9
- package/dist/os/spec/spec-status.js.map +1 -1
- package/dist/os/spec/spec-validate.js +7 -7
- package/dist/os/spec/spec-validate.js.map +1 -1
- package/dist/os/spec/spec.module.d.ts.map +1 -1
- package/dist/os/spec/spec.module.js +8 -0
- package/dist/os/spec/spec.module.js.map +1 -1
- package/dist/os/werkstatt/handlers/lock.js +16 -16
- package/dist/os/werkstatt/handlers/lock.js.map +1 -1
- package/dist/os/werkstatt/handlers/werkstatt-lock-recover.js +3 -3
- package/dist/os/werkstatt/handlers/werkstatt-lock-recover.js.map +1 -1
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.d.ts.map +1 -1
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js +3 -2
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js.map +1 -1
- package/dist/os/werkstatt/werkstatt.module.d.ts.map +1 -1
- package/dist/os/werkstatt/werkstatt.module.js +6 -2
- package/dist/os/werkstatt/werkstatt.module.js.map +1 -1
- package/dist/os/workflow/handlers.d.ts.map +1 -1
- package/dist/os/workflow/handlers.js +8 -5
- package/dist/os/workflow/handlers.js.map +1 -1
- package/dist/os/workflow/workflow.module.d.ts.map +1 -1
- package/dist/os/workflow/workflow.module.js +6 -3
- package/dist/os/workflow/workflow.module.js.map +1 -1
- package/dist/src/compass/contract-registry.js +1 -1
- package/dist/src/compass/contract-registry.js.map +1 -1
- package/dist/src/config/forge-config.d.ts +10 -1
- package/dist/src/config/forge-config.d.ts.map +1 -1
- package/dist/src/config/forge-config.js +8 -1
- package/dist/src/config/forge-config.js.map +1 -1
- package/dist/src/knowledge/budgets.js +1 -1
- package/dist/src/knowledge/budgets.js.map +1 -1
- package/dist/src/knowledge/compact.js +1 -1
- package/dist/src/knowledge/compact.js.map +1 -1
- package/dist/src/knowledge/parse.js +1 -1
- package/dist/src/knowledge/parse.js.map +1 -1
- package/dist/src/knowledge/sync.d.ts.map +1 -1
- package/dist/src/knowledge/sync.js +3 -5
- package/dist/src/knowledge/sync.js.map +1 -1
- package/dist/src/migration-adapters/git-utils.js +2 -2
- package/dist/src/migration-adapters/git-utils.js.map +1 -1
- package/dist/src/migration-adapters/ignored-files.js +2 -2
- package/dist/src/migration-adapters/ignored-files.js.map +1 -1
- package/dist/src/migration-adapters/node-typescript-pnpm/index.js +1 -1
- package/dist/src/migration-adapters/node-typescript-pnpm/index.js.map +1 -1
- package/dist/src/migration-adapters/phaser-pnpm/index.js +1 -1
- package/dist/src/migration-adapters/phaser-pnpm/index.js.map +1 -1
- package/dist/src/onboarding/agents-generate.d.ts +2 -0
- package/dist/src/onboarding/agents-generate.d.ts.map +1 -1
- package/dist/src/onboarding/agents-generate.js +73 -48
- package/dist/src/onboarding/agents-generate.js.map +1 -1
- package/dist/src/onboarding/create.d.ts.map +1 -1
- package/dist/src/onboarding/create.js +10 -10
- package/dist/src/onboarding/create.js.map +1 -1
- package/dist/src/onboarding/doctor.d.ts +6 -0
- package/dist/src/onboarding/doctor.d.ts.map +1 -1
- package/dist/src/onboarding/doctor.js +179 -61
- package/dist/src/onboarding/doctor.js.map +1 -1
- package/dist/src/onboarding/init.d.ts.map +1 -1
- package/dist/src/onboarding/init.js +30 -1
- package/dist/src/onboarding/init.js.map +1 -1
- package/dist/src/onboarding/invariant-engine.js +1 -1
- package/dist/src/onboarding/invariant-engine.js.map +1 -1
- package/dist/src/onboarding/memory-compact.d.ts +22 -0
- package/dist/src/onboarding/memory-compact.d.ts.map +1 -0
- package/dist/src/onboarding/memory-compact.js +148 -0
- package/dist/src/onboarding/memory-compact.js.map +1 -0
- package/dist/src/onboarding/memory-scaffold.d.ts +1 -0
- package/dist/src/onboarding/memory-scaffold.d.ts.map +1 -1
- package/dist/src/onboarding/memory-scaffold.js +2 -2
- package/dist/src/onboarding/memory-scaffold.js.map +1 -1
- package/dist/src/onboarding/nested-agents-generate.d.ts +4 -1
- package/dist/src/onboarding/nested-agents-generate.d.ts.map +1 -1
- package/dist/src/onboarding/nested-agents-generate.js +35 -6
- package/dist/src/onboarding/nested-agents-generate.js.map +1 -1
- package/dist/src/onboarding/nested-agents-templates.d.ts.map +1 -1
- package/dist/src/onboarding/nested-agents-templates.js +8 -12
- package/dist/src/onboarding/nested-agents-templates.js.map +1 -1
- package/dist/src/onboarding/npm-token-check.js +2 -2
- package/dist/src/onboarding/npm-token-check.js.map +1 -1
- package/dist/src/onboarding/prettierignore.d.ts +29 -0
- package/dist/src/onboarding/prettierignore.d.ts.map +1 -0
- package/dist/src/onboarding/prettierignore.js +231 -0
- package/dist/src/onboarding/prettierignore.js.map +1 -0
- package/dist/src/onboarding/profile-validate.d.ts.map +1 -1
- package/dist/src/onboarding/profile-validate.js +5 -4
- package/dist/src/onboarding/profile-validate.js.map +1 -1
- package/dist/src/onboarding/scaffold-project.d.ts.map +1 -1
- package/dist/src/onboarding/scaffold-project.js +35 -27
- package/dist/src/onboarding/scaffold-project.js.map +1 -1
- package/dist/src/onboarding/scaffold.js +1 -1
- package/dist/src/onboarding/scaffold.js.map +1 -1
- package/dist/src/onboarding/skill-markers.d.ts +27 -0
- package/dist/src/onboarding/skill-markers.d.ts.map +1 -0
- package/dist/src/onboarding/skill-markers.js +131 -0
- package/dist/src/onboarding/skill-markers.js.map +1 -0
- package/dist/src/onboarding/upgrade.d.ts +10 -0
- package/dist/src/onboarding/upgrade.d.ts.map +1 -1
- package/dist/src/onboarding/upgrade.js +215 -74
- package/dist/src/onboarding/upgrade.js.map +1 -1
- package/dist/src/onboarding/workspace-discovery.d.ts +1 -1
- package/dist/src/onboarding/workspace-discovery.d.ts.map +1 -1
- package/dist/src/onboarding/workspace-discovery.js +15 -3
- package/dist/src/onboarding/workspace-discovery.js.map +1 -1
- package/dist/src/pipeline-status.d.ts +6 -5
- package/dist/src/pipeline-status.d.ts.map +1 -1
- package/dist/src/pipeline-status.js +24 -19
- package/dist/src/pipeline-status.js.map +1 -1
- package/dist/src/profiles/stack-profile.js +1 -1
- package/dist/src/profiles/stack-profile.js.map +1 -1
- package/dist/src/registry.d.ts +1 -1
- package/dist/src/registry.d.ts.map +1 -1
- package/dist/src/registry.js +3 -3
- package/dist/src/registry.js.map +1 -1
- package/dist/src/skill-schema.d.ts +1 -1
- package/dist/src/skill-schema.d.ts.map +1 -1
- package/dist/src/skill-schema.js +4 -1
- package/dist/src/skill-schema.js.map +1 -1
- package/dist/src/types.d.ts +116 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/editable-region.d.ts +46 -0
- package/dist/src/utils/editable-region.d.ts.map +1 -0
- package/dist/src/utils/editable-region.js +125 -0
- package/dist/src/utils/editable-region.js.map +1 -0
- package/dist/src/utils/fs-idempotent.d.ts +2 -1
- package/dist/src/utils/fs-idempotent.d.ts.map +1 -1
- package/dist/src/utils/fs-idempotent.js +11 -7
- package/dist/src/utils/fs-idempotent.js.map +1 -1
- package/dist/src/utils/generated-marker.d.ts +17 -0
- package/dist/src/utils/generated-marker.d.ts.map +1 -1
- package/dist/src/utils/generated-marker.js +29 -1
- package/dist/src/utils/generated-marker.js.map +1 -1
- package/dist/src/utils/index.d.ts +3 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +3 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/io.d.ts +9 -0
- package/dist/src/utils/io.d.ts.map +1 -0
- package/dist/src/utils/io.js +130 -0
- package/dist/src/utils/io.js.map +1 -0
- package/dist/src/utils/markdown-table.d.ts +21 -0
- package/dist/src/utils/markdown-table.d.ts.map +1 -0
- package/dist/src/utils/markdown-table.js +116 -0
- package/dist/src/utils/markdown-table.js.map +1 -0
- package/dist/src/utils/sync-fs.d.ts +5 -0
- package/dist/src/utils/sync-fs.d.ts.map +1 -0
- package/dist/src/utils/sync-fs.js +26 -0
- package/dist/src/utils/sync-fs.js.map +1 -0
- package/dist/src/validators/note-frontmatter-validate.js +1 -1
- package/dist/src/validators/note-frontmatter-validate.js.map +1 -1
- package/dist/src/validators/note-link-validate.js +1 -1
- package/dist/src/validators/note-link-validate.js.map +1 -1
- package/dist/src/validators/note-orphan-detect.js +1 -1
- package/dist/src/validators/note-orphan-detect.js.map +1 -1
- package/dist/src/validators/port-validate.js +1 -1
- package/dist/src/validators/port-validate.js.map +1 -1
- package/dist/src/validators/skill-validate.d.ts.map +1 -1
- package/dist/src/validators/skill-validate.js +34 -13
- package/dist/src/validators/skill-validate.js.map +1 -1
- package/docs/reference/forge-yaml.md +12 -0
- package/os/adr/adr.module.ts +136 -132
- package/os/adr/frontmatter-io.ts +5 -5
- package/os/adr/handlers/archive.ts +8 -4
- package/os/adr/handlers/implement-stamp.ts +14 -9
- package/os/adr/handlers/list-create.ts +5 -5
- package/os/adr/handlers/validate.ts +1 -1
- package/os/audit/audit.module.ts +33 -34
- package/os/audit/frontmatter-io.ts +5 -5
- package/os/audit/handlers/archive.ts +8 -4
- package/os/compass/compass.module.ts +17 -1
- package/os/compass/handlers/compass-audit-handler.ts +13 -5
- package/os/compass/handlers/compass-change-summary-handler.ts +4 -2
- package/os/compass/handlers/compass-inventory-handler.ts +7 -4
- package/os/compass/handlers/compass-inventory.ts +11 -8
- package/os/compass/handlers/compass-migrate-handler.ts +1 -1
- package/os/compass/handlers/compass-migrate.ts +7 -3
- package/os/compass/handlers/git-revision.ts +11 -6
- package/os/compass/handlers/resolve-scan-root.ts +1 -1
- package/os/compass/handlers/summary-record.ts +10 -4
- package/os/compass/handlers/tests/compass-audit-plan.test.ts +2 -1
- package/os/core/core.module.ts +58 -4
- package/os/core/handlers/assets-helpers.ts +7 -3
- package/os/core/handlers/build.ts +1 -1
- package/os/core/handlers/determinism-check.ts +12 -6
- package/os/core/handlers/dev.ts +1 -1
- package/os/core/handlers/file-size-lint.ts +16 -9
- package/os/core/handlers/forge-autonomy-validate.ts +10 -5
- package/os/core/handlers/knowledge-compact.ts +1 -1
- package/os/core/handlers/package-health.ts +1 -1
- package/os/core/handlers/pinned-check.ts +2 -2
- package/os/core/handlers/pinned-init.ts +9 -8
- package/os/core/handlers/pinned-validate.ts +3 -3
- package/os/core/handlers/profile-resolve.ts +1 -1
- package/os/core/handlers/public-surface.ts +1 -1
- package/os/core/handlers/release-prepare.ts +11 -6
- package/os/core/handlers/release-publish.ts +6 -6
- package/os/core/handlers/validate.ts +1 -1
- package/os/exploration/exploration.module.ts +1 -0
- package/os/exploration/frontmatter-io.ts +4 -4
- package/os/exploration/handlers/archive.ts +3 -3
- package/os/mission/handlers/archive.ts +27 -19
- package/os/mission/mission.module.ts +38 -39
- package/os/naming/naming-convention.ts +9 -3
- package/os/naming/naming.module.ts +25 -25
- package/os/notes/notes.module.ts +87 -85
- package/os/plan/frontmatter-io.ts +5 -5
- package/os/plan/handlers/archive.ts +8 -4
- package/os/plan/plan.module.ts +33 -34
- package/os/plugin/plugin.module.ts +8 -6
- package/os/program/discovery.ts +2 -2
- package/os/program/handlers/complete.ts +1 -1
- package/os/program/handlers/lease.ts +1 -1
- package/os/program/handlers/seal.ts +1 -1
- package/os/program/lease.ts +1 -1
- package/os/program/program.module.ts +198 -193
- package/os/queue/index.ts +1 -1
- package/os/queue/manifest.ts +10 -9
- package/os/queue/queue-validate.test.ts +6 -15
- package/os/rfc/acceptance.ts +12 -6
- package/os/rfc/decision-log.ts +4 -3
- package/os/rfc/dna-trace.ts +8 -2
- package/os/rfc/frontmatter-io.ts +5 -5
- package/os/rfc/handlers/archive.ts +9 -5
- package/os/rfc/handlers/check.ts +5 -3
- package/os/rfc/handlers/implement-stamp.ts +15 -10
- package/os/rfc/handlers/index-graph.ts +3 -3
- package/os/rfc/handlers/lifecycle.ts +7 -2
- package/os/rfc/handlers/list-create.ts +7 -6
- package/os/rfc/handlers/shared.ts +3 -3
- package/os/rfc/handlers/supersede-propose.ts +8 -3
- package/os/rfc/handlers/validate-rules-rfc1006.test.ts +18 -6
- package/os/rfc/handlers/validate-rules.ts +10 -5
- package/os/rfc/rfc-create-concurrent.test.ts +11 -3
- package/os/rfc/rfc.module.ts +413 -374
- package/os/rfc/verification-evidence.ts +11 -5
- package/os/rfc/verification-refresh.ts +4 -3
- package/os/session/frontmatter-io.ts +14 -14
- package/os/session/handlers/archive.ts +8 -4
- package/os/session/handlers/metrics-aggregate.ts +34 -13
- package/os/session/handlers/metrics-rfc.ts +13 -8
- package/os/session/handlers/metrics-session.ts +7 -3
- package/os/session/handlers/save.ts +12 -8
- package/os/session/session.module.ts +6 -0
- package/os/spec/live-spec-list.ts +4 -4
- package/os/spec/live-spec-merge.ts +5 -5
- package/os/spec/live-spec-show.ts +3 -3
- package/os/spec/live-spec-validate.ts +4 -4
- package/os/spec/spec-materialize.ts +9 -9
- package/os/spec/spec-status.ts +9 -9
- package/os/spec/spec-validate.ts +7 -7
- package/os/spec/spec.module.ts +8 -0
- package/os/werkstatt/handlers/lock.ts +16 -16
- package/os/werkstatt/handlers/werkstatt-lock-recover.ts +3 -3
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +4 -2
- package/os/werkstatt/werkstatt.module.ts +8 -5
- package/os/workflow/handlers.ts +7 -5
- package/os/workflow/workflow.module.ts +45 -44
- package/package.json +1 -1
- package/profiles/forge-shell.yaml +3 -0
- package/profiles/godot-game.yaml +3 -0
- package/profiles/phaser-game.yaml +3 -0
- package/profiles/site-workshop.yaml +567 -564
- package/skills/fo/fo-add-tests/SKILL.md +2 -2
- package/skills/fo/fo-architecture/SKILL.md +2 -2
- package/skills/fo/fo-compass-annotate/SKILL.md +2 -2
- package/skills/fo/fo-design-summit/SKILL.md +2 -2
- package/skills/fo/fo-doc-audit/SKILL.md +2 -2
- package/skills/fo/fo-explore/SKILL.md +2 -2
- package/skills/fo/fo-extract-dna/SKILL.md +2 -2
- package/skills/fo/fo-fix/SKILL.md +2 -2
- package/skills/fo/fo-handoff/SKILL.md +2 -2
- package/skills/fo/fo-harvest/SKILL.md +2 -2
- package/skills/fo/fo-idea/SKILL.md +2 -2
- package/skills/fo/fo-idea-audit/SKILL.md +2 -2
- package/skills/fo/fo-idea-create-adr/SKILL.md +3 -3
- package/skills/fo/fo-idea-create-rfc/SKILL.md +3 -3
- package/skills/fo/fo-idea-enhance/SKILL.md +2 -2
- package/skills/fo/fo-idea-i-just-want-to-see-the-plan/SKILL.md +2 -2
- package/skills/fo/fo-idea-i-just-want-to-see-the-result/SKILL.md +1 -1
- package/skills/fo/fo-idea-implement/SKILL.md +2 -2
- package/skills/fo/fo-idea-plan/SKILL.md +2 -2
- package/skills/fo/fo-idea-status/SKILL.md +2 -2
- package/skills/fo/fo-knowledge-distill/SKILL.md +2 -2
- package/skills/fo/fo-memory-sync/SKILL.md +2 -2
- package/skills/fo/fo-qa/SKILL.md +2 -2
- package/skills/fo/fo-review/SKILL.md +2 -2
- package/skills/fo/fo-session-retro/SKILL.md +2 -2
- package/skills/fo/fo-session-save/SKILL.md +2 -2
- package/skills/fo/fo-spec-ingest/SKILL.md +2 -2
- package/skills/fo/fo-step-commit/SKILL.md +1 -1
- package/skills/fo/fo-triage/SKILL.md +2 -2
- package/skills/meta/forge-bootstrap/SKILL.md +1 -1
- package/skills/meta/port-to-forge/SKILL.md +1 -1
- package/skills/meta/skill-create/SKILL.md +1 -1
- package/skills/shared/grilling/SKILL.md +1 -1
- package/skills/shared/grilling/learned-principles.archive.md +17 -0
- package/skills/shared/grilling/learned-principles.md +0 -1
- package/skills/shared/knowledge/learned-principles.md +6 -0
- package/skills/shared/my-preferences/SKILL.md +1 -1
- package/skills/shared/windows-ai-tooling/SKILL.md +1 -1
- package/skills/shared/writing-great-skills/SKILL.md +1 -1
- package/src/compass/contract-registry.ts +1 -1
- package/src/config/forge-config.ts +12 -1
- package/src/knowledge/__tests__/sync.test.ts +2 -7
- package/src/knowledge/budgets.ts +1 -1
- package/src/knowledge/compact.ts +1 -1
- package/src/knowledge/parse.ts +1 -1
- package/src/knowledge/sync.ts +3 -5
- package/src/migration-adapters/git-utils.ts +2 -2
- package/src/migration-adapters/ignored-files.ts +2 -2
- package/src/migration-adapters/node-typescript-pnpm/index.ts +1 -1
- package/src/migration-adapters/phaser-pnpm/index.ts +1 -1
- package/src/onboarding/agents-generate.ts +91 -50
- package/src/onboarding/create.ts +10 -10
- package/src/onboarding/doctor.ts +199 -59
- package/src/onboarding/init.ts +38 -1
- package/src/onboarding/invariant-engine.ts +1 -1
- package/src/onboarding/memory-compact.ts +189 -0
- package/src/onboarding/memory-scaffold.ts +2 -2
- package/src/onboarding/nested-agents-generate.ts +50 -5
- package/src/onboarding/nested-agents-templates.ts +19 -13
- package/src/onboarding/npm-token-check.ts +2 -2
- package/src/onboarding/prettierignore.ts +263 -0
- package/src/onboarding/profile-validate.ts +5 -4
- package/src/onboarding/scaffold-project.ts +38 -27
- package/src/onboarding/scaffold.ts +1 -1
- package/src/onboarding/skill-markers.ts +162 -0
- package/src/onboarding/templates/behavioral-layer-core.md +2 -4
- package/src/onboarding/upgrade.ts +285 -89
- package/src/onboarding/workspace-discovery.ts +16 -2
- package/src/pipeline-status.test.ts +1 -4
- package/src/pipeline-status.ts +30 -13
- package/src/profiles/stack-profile.ts +1 -1
- package/src/registry.ts +4 -4
- package/src/skill-schema.ts +4 -1
- package/src/tests/acceptance-probe-kinds.test.ts +5 -15
- package/src/tests/agents-generate.test.ts +32 -7
- package/src/tests/contract-registry.test.ts +12 -6
- package/src/tests/doctor-dist-freshness.test.ts +110 -0
- package/src/tests/doctor-fix.test.ts +142 -0
- package/src/tests/doctor-nested-agents.test.ts +160 -0
- package/src/tests/editable-generated-region.test.ts +231 -0
- package/src/tests/file-size-lint.test.ts +8 -12
- package/src/tests/fixtures/agents-generate-business-before.txt +54 -54
- package/src/tests/markdown-table.test.ts +107 -0
- package/src/tests/memory-compact.test.ts +143 -0
- package/src/tests/prettierignore-manage.test.ts +134 -0
- package/src/tests/skill-agents-sync.test.ts +168 -0
- package/src/tests/skill-sync-marker.test.ts +134 -0
- package/src/tests/skill-validate.test.ts +12 -5
- package/src/tests/upgrade.test.ts +87 -41
- package/src/tests/workspace-discovery.test.ts +28 -0
- package/src/types.ts +123 -0
- package/src/utils/editable-region.ts +156 -0
- package/src/utils/fs-idempotent.ts +12 -6
- package/src/utils/generated-marker.ts +36 -2
- package/src/utils/index.ts +17 -0
- package/src/utils/io.ts +143 -0
- package/src/utils/markdown-table.ts +121 -0
- package/src/utils/sync-fs.ts +43 -0
- package/src/validators/note-frontmatter-validate.ts +1 -1
- package/src/validators/note-link-validate.ts +1 -1
- package/src/validators/note-orphan-detect.ts +1 -1
- package/src/validators/port-validate.ts +1 -1
- package/src/validators/skill-validate.ts +38 -13
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Unit tests for alignMarkdownTable + parseMarkdownTable (RFC-1154):
|
|
4
|
+
prettier-normal emission, separator dash-padding, escaped-pipe literals,
|
|
5
|
+
idempotency, and a regression guard over forge emission surfaces.</purpose>
|
|
6
|
+
</MODULE_CONTRACT>
|
|
7
|
+
<CHANGE_SUMMARY>
|
|
8
|
+
<item>RFC-1154: initial table-emitter tests + compact-separator regression guard.</item>
|
|
9
|
+
</CHANGE_SUMMARY>
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { test, expect } from "vitest";
|
|
13
|
+
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { alignMarkdownTable, parseMarkdownTable } from "../utils/markdown-table.ts";
|
|
16
|
+
|
|
17
|
+
test("aligns cells and pads separator with dashes to column width", () => {
|
|
18
|
+
const out = alignMarkdownTable(
|
|
19
|
+
["Name", "Value"],
|
|
20
|
+
[
|
|
21
|
+
["long-name-here", "x"],
|
|
22
|
+
["a", "b"],
|
|
23
|
+
],
|
|
24
|
+
);
|
|
25
|
+
expect(out).toBe(
|
|
26
|
+
[
|
|
27
|
+
"| Name | Value |",
|
|
28
|
+
"| -------------- | ----- |",
|
|
29
|
+
"| long-name-here | x |",
|
|
30
|
+
"| a | b |",
|
|
31
|
+
].join("\n"),
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("minimum column width is 3 even for short cells", () => {
|
|
36
|
+
const out = alignMarkdownTable(["a", "b"], [["1", "2"]]);
|
|
37
|
+
expect(out).toBe("| a | b |\n| --- | --- |\n| 1 | 2 |");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("escaped pipe inside a cell is a literal, not a delimiter", () => {
|
|
41
|
+
const out = alignMarkdownTable(["Pattern", "Meaning"], [["a \\| b", "or"]]);
|
|
42
|
+
const lines = out.split("\n");
|
|
43
|
+
expect(lines[0]).toBe("| Pattern | Meaning |");
|
|
44
|
+
expect(lines[1]).toBe("| ------- | ------- |");
|
|
45
|
+
expect(lines[2]).toBe("| a \\| b | or |");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("rows shorter or longer than headers extend the column set", () => {
|
|
49
|
+
const out = alignMarkdownTable(["A"], [["x", "extra"]]);
|
|
50
|
+
expect(out.split("\n")[0]).toBe("| A | |");
|
|
51
|
+
expect(out.split("\n")[2]).toBe("| x | extra |");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("empty input emits empty string", () => {
|
|
55
|
+
expect(alignMarkdownTable([], [])).toBe("");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("AC-5: re-aligning emitted output is byte-identical", () => {
|
|
59
|
+
const once = alignMarkdownTable(
|
|
60
|
+
["Name", "Category", "Concerns"],
|
|
61
|
+
[
|
|
62
|
+
["fo-idea", "fo", "classification"],
|
|
63
|
+
["wg-mission", "wg", "lifecycle"],
|
|
64
|
+
],
|
|
65
|
+
);
|
|
66
|
+
const parsed = parseMarkdownTable(once);
|
|
67
|
+
expect(parsed).not.toBeNull();
|
|
68
|
+
const twice = alignMarkdownTable(parsed!.headers, parsed!.rows);
|
|
69
|
+
expect(twice, "align(parse(emit)) must be byte-stable — check separator width math").toBe(once);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("parseMarkdownTable returns null for non-table input", () => {
|
|
73
|
+
expect(parseMarkdownTable("just prose\nno table here")).toBeNull();
|
|
74
|
+
expect(parseMarkdownTable("| a |\nno separator")).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("parseMarkdownTable splits on unescaped pipes only", () => {
|
|
78
|
+
const parsed = parseMarkdownTable("| a \\| b | c |\n| --- | --- |\n| 1 | 2 |");
|
|
79
|
+
expect(parsed).not.toBeNull();
|
|
80
|
+
expect(parsed!.headers).toEqual(["a \\| b", "c"]);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("regression guard: no forge emitter emits a compact '| --- |' separator", () => {
|
|
84
|
+
const srcDir = join(import.meta.dirname, "..");
|
|
85
|
+
const offenders: string[] = [];
|
|
86
|
+
const walk = (dir: string) => {
|
|
87
|
+
for (const entry of readdirSync(dir)) {
|
|
88
|
+
const full = join(dir, entry);
|
|
89
|
+
if (statSync(full).isDirectory()) {
|
|
90
|
+
if (entry === "tests" || entry === "__tests__") continue;
|
|
91
|
+
walk(full);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!entry.endsWith(".ts")) continue;
|
|
95
|
+
const content = readFileSync(full, "utf8");
|
|
96
|
+
// Flag compact separator literals — all tables must route through
|
|
97
|
+
// alignMarkdownTable. The aligner itself lives in utils/markdown-table.ts.
|
|
98
|
+
if (full.endsWith("markdown-table.ts")) continue;
|
|
99
|
+
if (/\| -{2,} \|/.test(content)) offenders.push(full);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
walk(srcDir);
|
|
103
|
+
expect(
|
|
104
|
+
offenders,
|
|
105
|
+
"compact '| --- |' table literals found — route through alignMarkdownTable",
|
|
106
|
+
).toEqual([]);
|
|
107
|
+
});
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Unit tests for forge memory.compact — mechanical MEMORY.md budget
|
|
4
|
+
enforcement via oldest-first Environment notes truncation (RFC-1151).</purpose>
|
|
5
|
+
</MODULE_CONTRACT>
|
|
6
|
+
<CHANGE_SUMMARY>
|
|
7
|
+
<item>RFC-1151: initial memory.compact tests.</item>
|
|
8
|
+
</CHANGE_SUMMARY>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { test, expect, beforeEach, afterEach } from "vitest";
|
|
12
|
+
import { mkdtemp, rm, mkdir, writeFile, readFile } from "node:fs/promises";
|
|
13
|
+
import { tmpdir } from "node:os";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { runMemoryCompact, compactMemoryMd } from "../onboarding/memory-compact.ts";
|
|
16
|
+
import type { ForgeRuntimeContext } from "../types.ts";
|
|
17
|
+
|
|
18
|
+
let tempDir: string;
|
|
19
|
+
|
|
20
|
+
beforeEach(async () => {
|
|
21
|
+
tempDir = await mkdtemp(join(tmpdir(), "memory-compact-test-"));
|
|
22
|
+
await mkdir(join(tempDir, ".agents", "memory"), { recursive: true });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function makeContext(dryRun = false): ForgeRuntimeContext {
|
|
30
|
+
return {
|
|
31
|
+
workspaceRoot: tempDir,
|
|
32
|
+
logger: {
|
|
33
|
+
section: () => {},
|
|
34
|
+
success: () => {},
|
|
35
|
+
warn: () => {},
|
|
36
|
+
error: () => {},
|
|
37
|
+
info: () => {},
|
|
38
|
+
},
|
|
39
|
+
dryRun,
|
|
40
|
+
outputFormat: "json",
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function memoryMd(envBullets: number, bulletSize = 200): string {
|
|
45
|
+
const env = Array.from(
|
|
46
|
+
{ length: envBullets },
|
|
47
|
+
(_, i) => `- env note ${i} ${"x".repeat(bulletSize)}`,
|
|
48
|
+
).join("\n");
|
|
49
|
+
return [
|
|
50
|
+
"# Project Memory",
|
|
51
|
+
"",
|
|
52
|
+
"## Current focus",
|
|
53
|
+
"",
|
|
54
|
+
"- keep me",
|
|
55
|
+
"",
|
|
56
|
+
"## Decisions in flight",
|
|
57
|
+
"",
|
|
58
|
+
"- keep me too",
|
|
59
|
+
"",
|
|
60
|
+
"## Environment notes",
|
|
61
|
+
"",
|
|
62
|
+
env,
|
|
63
|
+
"",
|
|
64
|
+
].join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
test("compactMemoryMd removes oldest Environment notes bullets until within budget", () => {
|
|
68
|
+
const content = memoryMd(20);
|
|
69
|
+
const budget = 2000;
|
|
70
|
+
const { lines, removedLines, fitsBudget } = compactMemoryMd(content, budget);
|
|
71
|
+
|
|
72
|
+
expect(fitsBudget).toBe(true);
|
|
73
|
+
expect(removedLines.length).toBeGreaterThan(0);
|
|
74
|
+
// Oldest-first: env note 0 removed before env note 19
|
|
75
|
+
expect(removedLines[0]).toContain("env note 0");
|
|
76
|
+
expect(lines.join("\n").length).toBeLessThanOrEqual(budget);
|
|
77
|
+
// Protected sections untouched
|
|
78
|
+
expect(lines.join("\n")).toContain("- keep me");
|
|
79
|
+
expect(lines.join("\n")).toContain("- keep me too");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("compactMemoryMd returns empty removedLines when already within budget", () => {
|
|
83
|
+
const content = memoryMd(2, 10);
|
|
84
|
+
const { removedLines, fitsBudget } = compactMemoryMd(content, 4096);
|
|
85
|
+
expect(removedLines).toHaveLength(0);
|
|
86
|
+
expect(fitsBudget).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("runMemoryCompact writes compacted file and reports removed count", async () => {
|
|
90
|
+
const content = memoryMd(30);
|
|
91
|
+
await writeFile(join(tempDir, ".agents", "memory", "MEMORY.md"), content, "utf8");
|
|
92
|
+
|
|
93
|
+
const result = await runMemoryCompact({ argv: [], flags: {} }, makeContext());
|
|
94
|
+
expect(result.exitCode).toBe(0);
|
|
95
|
+
expect(result.data?.status).toBe("pass");
|
|
96
|
+
expect(result.data?.removed).toBeGreaterThan(0);
|
|
97
|
+
expect(result.data?.remainingChars).toBeLessThanOrEqual(result.data!.budget);
|
|
98
|
+
|
|
99
|
+
const after = await readFile(join(tempDir, ".agents", "memory", "MEMORY.md"), "utf8");
|
|
100
|
+
expect(after.length).toBeLessThanOrEqual(result.data!.budget);
|
|
101
|
+
expect(after).toContain("- keep me");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("runMemoryCompact dryRun reports without writing", async () => {
|
|
105
|
+
const content = memoryMd(30);
|
|
106
|
+
await writeFile(join(tempDir, ".agents", "memory", "MEMORY.md"), content, "utf8");
|
|
107
|
+
|
|
108
|
+
const result = await runMemoryCompact({ argv: [], flags: {} }, makeContext(true));
|
|
109
|
+
expect(result.exitCode).toBe(0);
|
|
110
|
+
expect(result.data?.removed).toBeGreaterThan(0);
|
|
111
|
+
expect(result.data?.removedLines?.length).toBe(result.data?.removed);
|
|
112
|
+
|
|
113
|
+
const after = await readFile(join(tempDir, ".agents", "memory", "MEMORY.md"), "utf8");
|
|
114
|
+
expect(after).toBe(content);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("runMemoryCompact passes when MEMORY.md absent", async () => {
|
|
118
|
+
const result = await runMemoryCompact({ argv: [], flags: {} }, makeContext());
|
|
119
|
+
expect(result.exitCode).toBe(0);
|
|
120
|
+
expect(result.data?.removed).toBe(0);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("runMemoryCompact fails when protected sections alone exceed budget", async () => {
|
|
124
|
+
// Huge Current focus — Environment notes empty, nothing removable
|
|
125
|
+
const content = [
|
|
126
|
+
"# Project Memory",
|
|
127
|
+
"",
|
|
128
|
+
"## Current focus",
|
|
129
|
+
"",
|
|
130
|
+
`- ${"y".repeat(6000)}`,
|
|
131
|
+
"",
|
|
132
|
+
"## Environment notes",
|
|
133
|
+
"",
|
|
134
|
+
"- small note",
|
|
135
|
+
"",
|
|
136
|
+
].join("\n");
|
|
137
|
+
await writeFile(join(tempDir, ".agents", "memory", "MEMORY.md"), content, "utf8");
|
|
138
|
+
|
|
139
|
+
const result = await runMemoryCompact({ argv: [], flags: {} }, makeContext());
|
|
140
|
+
expect(result.exitCode).toBe(1);
|
|
141
|
+
expect(result.data?.status).toBe("fail");
|
|
142
|
+
expect(result.data?.errors[0]).toContain("manual editorial compaction");
|
|
143
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Unit tests for the RFC-1154 managed .prettierignore block:
|
|
4
|
+
root-only prettier detection, delimited-block reconcile preserving operator
|
|
5
|
+
content, marker-driven skill entries, and the skipped contract for
|
|
6
|
+
prettier-free consumers.</purpose>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-1154: initial prettierignore plan/apply tests.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { test, expect, beforeEach, afterEach } from "vitest";
|
|
14
|
+
import { mkdtemp, rm, writeFile, mkdir, readFile } from "node:fs/promises";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import {
|
|
19
|
+
planPrettierignore,
|
|
20
|
+
applyPrettierignore,
|
|
21
|
+
reconcilePrettierignoreContent,
|
|
22
|
+
PRETTIERIGNORE_BLOCK_BEGIN,
|
|
23
|
+
PRETTIERIGNORE_BLOCK_END,
|
|
24
|
+
} from "../onboarding/prettierignore.ts";
|
|
25
|
+
import { SKILL_MARKER_FILE } from "../onboarding/skill-markers.ts";
|
|
26
|
+
|
|
27
|
+
let tmpDir: string;
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
tmpDir = await mkdtemp(join(tmpdir(), "prettierignore-"));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
async function withPrettier(root: string): Promise<void> {
|
|
38
|
+
await writeFile(join(root, ".prettierrc"), "{}\n");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
test("detects prettier via root .prettierrc", async () => {
|
|
42
|
+
await withPrettier(tmpDir);
|
|
43
|
+
const plan = await planPrettierignore(tmpDir);
|
|
44
|
+
expect(plan.applies).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("detects prettier via package.json prettier key", async () => {
|
|
48
|
+
await writeFile(join(tmpDir, "package.json"), JSON.stringify({ prettier: {} }));
|
|
49
|
+
const plan = await planPrettierignore(tmpDir);
|
|
50
|
+
expect(plan.applies).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("detects prettier via prettier devDependency", async () => {
|
|
54
|
+
await writeFile(
|
|
55
|
+
join(tmpDir, "package.json"),
|
|
56
|
+
JSON.stringify({ devDependencies: { prettier: "^3" } }),
|
|
57
|
+
);
|
|
58
|
+
const plan = await planPrettierignore(tmpDir);
|
|
59
|
+
expect(plan.applies).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("detects prettier via existing .prettierignore alone", async () => {
|
|
63
|
+
await writeFile(join(tmpDir, ".prettierignore"), "dist/\n");
|
|
64
|
+
const plan = await planPrettierignore(tmpDir);
|
|
65
|
+
expect(plan.applies).toBe(true);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("AC-4: no prettier markers — plan does not apply, apply writes nothing", async () => {
|
|
69
|
+
const plan = await planPrettierignore(tmpDir);
|
|
70
|
+
expect(plan.applies).toBe(false);
|
|
71
|
+
const result = await applyPrettierignore(tmpDir, plan);
|
|
72
|
+
expect(result).toBe("skipped");
|
|
73
|
+
expect(existsSync(join(tmpDir, ".prettierignore"))).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("AC-2: block contains generated.yaml glob, sessions, metrics, queues entries", async () => {
|
|
77
|
+
await withPrettier(tmpDir);
|
|
78
|
+
const plan = await planPrettierignore(tmpDir);
|
|
79
|
+
const result = await applyPrettierignore(tmpDir, plan);
|
|
80
|
+
expect(result).toBe("written");
|
|
81
|
+
const content = await readFile(join(tmpDir, ".prettierignore"), "utf8");
|
|
82
|
+
expect(content).toContain(PRETTIERIGNORE_BLOCK_BEGIN);
|
|
83
|
+
expect(content).toContain("**/*.generated.yaml");
|
|
84
|
+
expect(content).toContain("docs/sessions/");
|
|
85
|
+
expect(content).toContain("docs/metrics/");
|
|
86
|
+
expect(content).toContain("docs/queues/session-*.yaml");
|
|
87
|
+
expect(content).toContain(PRETTIERIGNORE_BLOCK_END);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("skill entries enumerate only marker-carrying dirs (AC-9)", async () => {
|
|
91
|
+
await withPrettier(tmpDir);
|
|
92
|
+
const skillsDir = join(tmpDir, ".agents", "skills");
|
|
93
|
+
await mkdir(join(skillsDir, "fo-idea"), { recursive: true });
|
|
94
|
+
await writeFile(join(skillsDir, "fo-idea", SKILL_MARKER_FILE), '{"files":["SKILL.md"]}');
|
|
95
|
+
await mkdir(join(skillsDir, "my-own-skill"), { recursive: true }); // unmarked — consumer-owned
|
|
96
|
+
|
|
97
|
+
const plan = await planPrettierignore(tmpDir);
|
|
98
|
+
expect(plan.entries).toContain(".agents/skills/fo-idea/");
|
|
99
|
+
expect(plan.entries).not.toContain(".agents/skills/my-own-skill/");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("reconcile preserves operator content outside the block", async () => {
|
|
103
|
+
const existing = `dist/\nnode_modules/\n${PRETTIERIGNORE_BLOCK_BEGIN}\nold-entry/\n${PRETTIERIGNORE_BLOCK_END}\ncustom/\n`;
|
|
104
|
+
const { next, changed } = reconcilePrettierignoreContent(existing, ["a/", "b/"]);
|
|
105
|
+
expect(changed).toBe("updated");
|
|
106
|
+
expect(next).toContain("dist/\nnode_modules/\n");
|
|
107
|
+
expect(next).toContain("custom/\n");
|
|
108
|
+
expect(next).not.toContain("old-entry/");
|
|
109
|
+
expect(next).toContain("a/\nb/");
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("unbalanced markers treat the file as operator-owned and append a fresh block", async () => {
|
|
113
|
+
const existing = `dist/\n${PRETTIERIGNORE_BLOCK_BEGIN}\nstray/\n`; // no END marker
|
|
114
|
+
const { next, changed } = reconcilePrettierignoreContent(existing, ["x/"]);
|
|
115
|
+
expect(changed).toBe("updated");
|
|
116
|
+
expect(next).toContain("dist/\n");
|
|
117
|
+
// Original content preserved verbatim — forge does not merge into a
|
|
118
|
+
// corrupted region; a fresh block is appended.
|
|
119
|
+
expect(next.indexOf(PRETTIERIGNORE_BLOCK_BEGIN)).toBeLessThan(
|
|
120
|
+
next.lastIndexOf(PRETTIERIGNORE_BLOCK_BEGIN),
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("repeated apply is idempotent", async () => {
|
|
125
|
+
await withPrettier(tmpDir);
|
|
126
|
+
await writeFile(join(tmpDir, ".prettierignore"), "dist/\n");
|
|
127
|
+
const plan = await planPrettierignore(tmpDir);
|
|
128
|
+
const first = await applyPrettierignore(tmpDir, plan);
|
|
129
|
+
expect(first).toBe("updated");
|
|
130
|
+
const second = await applyPrettierignore(tmpDir, await planPrettierignore(tmpDir));
|
|
131
|
+
expect(second, "second apply must be unchanged — check reconcile block boundaries").toBe(
|
|
132
|
+
"unchanged",
|
|
133
|
+
);
|
|
134
|
+
});
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>CI gate: every forge/pack skill source must be fully propagated to
|
|
4
|
+
`.agents/skills/` (IDE discovery copy written by forge.init/forge.upgrade).
|
|
5
|
+
SKILL.md syncs verbatim; declared knowledge files follow the append-only
|
|
6
|
+
syncKnowledgeFile contract — in the canonical repo a resync must be a no-op,
|
|
7
|
+
otherwise source and copy have drifted apart.</purpose>
|
|
8
|
+
<non-goals>
|
|
9
|
+
<item>Do not validate skill content/schema — forge.skill.validate owns that.</item>
|
|
10
|
+
<item>Do not flag unmarked dirs — a missing .forge-managed marker means
|
|
11
|
+
consumer-owned (RFC-1154), e.g. `_shared/`.</item>
|
|
12
|
+
</non-goals>
|
|
13
|
+
<KEY_DECISIONS>
|
|
14
|
+
<item>planKnowledgeSync is reused so the gate encodes the exact merge
|
|
15
|
+
semantics of the syncer — only "unchanged" is a pass; "merged" (source has
|
|
16
|
+
entries the copy lacks), "copied" (copy missing), and "skipped"
|
|
17
|
+
(non-mergeable divergence = hand-edited copy) all fail.</item>
|
|
18
|
+
<item>Runs on the real repo — this is a drift gate, not a fixture test.</item>
|
|
19
|
+
</KEY_DECISIONS>
|
|
20
|
+
<CHANGE_SUMMARY>
|
|
21
|
+
<item>Initial version — covers FORGE_SKILLS, declared pack skills
|
|
22
|
+
(discoverPackSkills), and the shared-knowledge layer.</item>
|
|
23
|
+
</CHANGE_SUMMARY>
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import fs from "node:fs";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
import { describe, expect, it } from "vitest";
|
|
29
|
+
|
|
30
|
+
import { loadForgeConfig } from "../config/forge-config.ts";
|
|
31
|
+
import { planKnowledgeSync } from "../knowledge/index.ts";
|
|
32
|
+
import { FORGE_SKILLS, discoverPackSkills } from "../registry.ts";
|
|
33
|
+
|
|
34
|
+
const forgeRoot = path.resolve(import.meta.dirname, "../..");
|
|
35
|
+
const workspaceRoot = path.resolve(import.meta.dirname, "../../../..");
|
|
36
|
+
|
|
37
|
+
interface SkillSource {
|
|
38
|
+
name: string;
|
|
39
|
+
srcDir: string;
|
|
40
|
+
knowledge: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function collectSkillSources(): SkillSource[] {
|
|
44
|
+
const sources: SkillSource[] = [];
|
|
45
|
+
for (const skill of FORGE_SKILLS) {
|
|
46
|
+
sources.push({
|
|
47
|
+
name: skill.name,
|
|
48
|
+
srcDir: path.dirname(path.join(forgeRoot, skill.path)),
|
|
49
|
+
knowledge: skill.knowledge ?? [],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const config = loadForgeConfig(workspaceRoot);
|
|
53
|
+
for (const skill of discoverPackSkills(workspaceRoot, config)) {
|
|
54
|
+
sources.push({
|
|
55
|
+
name: skill.name,
|
|
56
|
+
srcDir: path.dirname(path.join(workspaceRoot, skill.dir, skill.path)),
|
|
57
|
+
knowledge: skill.knowledge ?? [],
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
return sources;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Skipped in the standalone package repo — the gate needs a forge-managed
|
|
64
|
+
// workspace root (forge.yaml + synced skillsDir), which only exists for the
|
|
65
|
+
// monorepo and consumer projects, not for the published forge source tree.
|
|
66
|
+
const hasForgeWorkspace = fs.existsSync(path.join(workspaceRoot, "forge.yaml"));
|
|
67
|
+
describe.runIf(hasForgeWorkspace)(
|
|
68
|
+
".agents/skills sync gate (forge.init/forge.upgrade parity)",
|
|
69
|
+
() => {
|
|
70
|
+
// describe callbacks still run during collection even when runIf is false,
|
|
71
|
+
// so initializers must tolerate the absent forge.yaml themselves.
|
|
72
|
+
const config = hasForgeWorkspace ? loadForgeConfig(workspaceRoot) : null;
|
|
73
|
+
const skillsDir = config ? path.join(workspaceRoot, config.paths.skillsDir) : "";
|
|
74
|
+
const sources = config ? collectSkillSources() : [];
|
|
75
|
+
|
|
76
|
+
it("every forge + pack skill has a synced copy in .agents/skills/", () => {
|
|
77
|
+
const missing = sources
|
|
78
|
+
.filter((s) => !fs.existsSync(path.join(skillsDir, s.name, "SKILL.md")))
|
|
79
|
+
.map((s) => `${s.name} (src ${path.relative(workspaceRoot, s.srcDir)})`);
|
|
80
|
+
expect(
|
|
81
|
+
missing,
|
|
82
|
+
`skills never synced — run \`forge init\`/\`forge upgrade\` to populate ${path.relative(workspaceRoot, skillsDir)}`,
|
|
83
|
+
).toEqual([]);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("SKILL.md copies are byte-identical to their sources", () => {
|
|
87
|
+
const drifted: string[] = [];
|
|
88
|
+
for (const s of sources) {
|
|
89
|
+
const src = path.join(s.srcDir, "SKILL.md");
|
|
90
|
+
const dest = path.join(skillsDir, s.name, "SKILL.md");
|
|
91
|
+
if (!fs.existsSync(dest)) continue; // reported by the presence test
|
|
92
|
+
if (fs.readFileSync(src, "utf8") !== fs.readFileSync(dest, "utf8")) {
|
|
93
|
+
drifted.push(s.name);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
expect(
|
|
97
|
+
drifted,
|
|
98
|
+
"SKILL.md drift — edit the pack source and re-sync, never edit .agents/skills/ directly",
|
|
99
|
+
).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("declared knowledge files are in sync (append-only merge would be a no-op)", () => {
|
|
103
|
+
const stale: string[] = [];
|
|
104
|
+
for (const s of sources) {
|
|
105
|
+
for (const kf of s.knowledge) {
|
|
106
|
+
const src = path.join(s.srcDir, kf);
|
|
107
|
+
const dest = path.join(skillsDir, s.name, kf);
|
|
108
|
+
if (!fs.existsSync(src)) {
|
|
109
|
+
stale.push(`${s.name}/${kf} — declared but missing in source`);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (!fs.existsSync(dest)) {
|
|
113
|
+
stale.push(`${s.name}/${kf} — missing in .agents/skills (run forge upgrade)`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const plan = planKnowledgeSync(src, dest);
|
|
117
|
+
if (plan.action === "merged") {
|
|
118
|
+
stale.push(`${s.name}/${kf} — source has entries the copy lacks`);
|
|
119
|
+
} else if (plan.action === "skipped") {
|
|
120
|
+
stale.push(`${s.name}/${kf} — copy diverged non-mergeably (hand-edited .agents copy)`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
expect(stale).toEqual([]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("undeclared aux files in the copy still match their sources", () => {
|
|
128
|
+
// The syncer writes SKILL.md + declared knowledge files only. Aux files
|
|
129
|
+
// (references, templates, placeholder dirs) that exist in BOTH trees are
|
|
130
|
+
// hand-copied and silently diverge — a same-named pair must stay identical,
|
|
131
|
+
// and a copy-only orphan can never resync.
|
|
132
|
+
const flagged: string[] = [];
|
|
133
|
+
for (const s of sources) {
|
|
134
|
+
const destDir = path.join(skillsDir, s.name);
|
|
135
|
+
if (!fs.existsSync(destDir)) continue;
|
|
136
|
+
const declared = new Set(["SKILL.md", ".forge-managed", ...s.knowledge]);
|
|
137
|
+
for (const entry of fs.readdirSync(destDir)) {
|
|
138
|
+
if (declared.has(entry)) continue;
|
|
139
|
+
const srcEntry = path.join(s.srcDir, entry);
|
|
140
|
+
const destEntry = path.join(destDir, entry);
|
|
141
|
+
if (fs.statSync(destEntry).isDirectory()) {
|
|
142
|
+
if (!fs.existsSync(srcEntry))
|
|
143
|
+
flagged.push(`${s.name}/${entry}/ — dir absent in source`);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (!fs.existsSync(srcEntry)) {
|
|
147
|
+
flagged.push(`${s.name}/${entry} — absent in source, copy can never resync`);
|
|
148
|
+
} else if (fs.readFileSync(srcEntry, "utf8") !== fs.readFileSync(destEntry, "utf8")) {
|
|
149
|
+
flagged.push(`${s.name}/${entry} — diverged from source`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
expect(
|
|
154
|
+
flagged,
|
|
155
|
+
"aux-file drift — declare aux files via `knowledge:` in SKILL.md or keep copies identical to source",
|
|
156
|
+
).toEqual([]);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("shared-knowledge layer is synced", () => {
|
|
160
|
+
const src = path.join(forgeRoot, "skills", "shared", "knowledge", "learned-principles.md");
|
|
161
|
+
const dest = path.join(skillsDir, "shared-knowledge", "learned-principles.md");
|
|
162
|
+
if (!fs.existsSync(src)) return; // layer absent — nothing to gate
|
|
163
|
+
expect(fs.existsSync(dest), "shared-knowledge/learned-principles.md not synced").toBe(true);
|
|
164
|
+
const plan = planKnowledgeSync(src, dest);
|
|
165
|
+
expect(plan.action, `shared-knowledge drift (sync action: ${plan.action})`).toBe("unchanged");
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
);
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Unit tests for RFC-1154 skill markers: .forge-managed manifests
|
|
4
|
+
recorded on sync, and the stale-dir prune under the subset-of-manifest rule
|
|
5
|
+
(keeps dirs with consumer files or corrupt markers, never touches unmarked
|
|
6
|
+
dirs).</purpose>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-1154: initial marker + prune tests.</item>
|
|
10
|
+
</CHANGE_SUMMARY>
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { test, expect, beforeEach, afterEach } from "vitest";
|
|
14
|
+
import { mkdtemp, rm, writeFile, mkdir, readFile } from "node:fs/promises";
|
|
15
|
+
import { existsSync } from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import {
|
|
19
|
+
SKILL_MARKER_FILE,
|
|
20
|
+
writeSkillMarker,
|
|
21
|
+
writeSkillMarkerSync,
|
|
22
|
+
pruneStaleSkillDirs,
|
|
23
|
+
} from "../onboarding/skill-markers.ts";
|
|
24
|
+
|
|
25
|
+
let tmpDir: string;
|
|
26
|
+
let skillsDir: string;
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
tmpDir = await mkdtemp(join(tmpdir(), "skill-marker-"));
|
|
30
|
+
skillsDir = join(tmpDir, ".agents", "skills");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
async function makeSkillDir(name: string, files: string[]): Promise<void> {
|
|
38
|
+
const dir = join(skillsDir, name);
|
|
39
|
+
await mkdir(dir, { recursive: true });
|
|
40
|
+
for (const f of files) {
|
|
41
|
+
const filePath = join(dir, f);
|
|
42
|
+
await mkdir(join(filePath, ".."), { recursive: true });
|
|
43
|
+
await writeFile(filePath, `content of ${f}\n`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
test("AC-7: writeSkillMarker records the exact file list", async () => {
|
|
48
|
+
const dir = join(skillsDir, "fo-idea");
|
|
49
|
+
await mkdir(dir, { recursive: true });
|
|
50
|
+
await writeSkillMarker(dir, ["SKILL.md", "qa-log.md"]);
|
|
51
|
+
const raw = await readFile(join(dir, SKILL_MARKER_FILE), "utf8");
|
|
52
|
+
expect(JSON.parse(raw)).toEqual({ files: ["SKILL.md", "qa-log.md"] });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("marker write is idempotent (identical content on repeat)", async () => {
|
|
56
|
+
const dir = join(skillsDir, "fo-idea");
|
|
57
|
+
await mkdir(dir, { recursive: true });
|
|
58
|
+
writeSkillMarkerSync(dir, ["b.md", "a.md"]);
|
|
59
|
+
const first = await readFile(join(dir, SKILL_MARKER_FILE), "utf8");
|
|
60
|
+
writeSkillMarkerSync(dir, ["b.md", "a.md"]);
|
|
61
|
+
expect(await readFile(join(dir, SKILL_MARKER_FILE), "utf8")).toBe(first);
|
|
62
|
+
expect(JSON.parse(first).files).toEqual(["a.md", "b.md"]); // sorted, deterministic
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("AC-8: prunes a stale marked dir whose contents match the manifest", async () => {
|
|
66
|
+
const dir = join(skillsDir, "old-skill");
|
|
67
|
+
await makeSkillDir("old-skill", ["SKILL.md"]);
|
|
68
|
+
await writeSkillMarker(dir, ["SKILL.md"]);
|
|
69
|
+
|
|
70
|
+
const result = await pruneStaleSkillDirs(skillsDir, new Set(["other-skill"]));
|
|
71
|
+
expect(result.pruned).toEqual(["old-skill"]);
|
|
72
|
+
expect(result.keptWithConsumerFiles).toEqual([]);
|
|
73
|
+
expect(existsSync(dir)).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("AC-8: keeps a stale marked dir containing consumer files", async () => {
|
|
77
|
+
const dir = join(skillsDir, "old-skill");
|
|
78
|
+
await makeSkillDir("old-skill", ["SKILL.md", "my-notes.md"]);
|
|
79
|
+
await writeSkillMarker(dir, ["SKILL.md"]);
|
|
80
|
+
|
|
81
|
+
const result = await pruneStaleSkillDirs(skillsDir, new Set([]));
|
|
82
|
+
expect(result.pruned).toEqual([]);
|
|
83
|
+
expect(result.keptWithConsumerFiles).toHaveLength(1);
|
|
84
|
+
expect(result.keptWithConsumerFiles[0]).toContain("old-skill");
|
|
85
|
+
expect(result.keptWithConsumerFiles[0]).toContain("my-notes.md");
|
|
86
|
+
expect(existsSync(dir)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("AC-9: unmarked dirs are never pruned", async () => {
|
|
90
|
+
const dir = join(skillsDir, "consumer-skill");
|
|
91
|
+
await makeSkillDir("consumer-skill", ["SKILL.md"]);
|
|
92
|
+
|
|
93
|
+
const result = await pruneStaleSkillDirs(skillsDir, new Set([]));
|
|
94
|
+
expect(result.pruned).toEqual([]);
|
|
95
|
+
expect(result.keptWithConsumerFiles).toEqual([]);
|
|
96
|
+
expect(existsSync(dir)).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("dirs still in the sync set are kept regardless of marker", async () => {
|
|
100
|
+
const dir = join(skillsDir, "current-skill");
|
|
101
|
+
await makeSkillDir("current-skill", ["SKILL.md"]);
|
|
102
|
+
await writeSkillMarker(dir, ["SKILL.md"]);
|
|
103
|
+
|
|
104
|
+
const result = await pruneStaleSkillDirs(skillsDir, new Set(["current-skill"]));
|
|
105
|
+
expect(result.pruned).toEqual([]);
|
|
106
|
+
expect(existsSync(dir)).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("corrupt marker disables prune for that dir (fail-safe keep)", async () => {
|
|
110
|
+
const dir = join(skillsDir, "broken-skill");
|
|
111
|
+
await makeSkillDir("broken-skill", ["SKILL.md"]);
|
|
112
|
+
await writeFile(join(dir, SKILL_MARKER_FILE), "not json {{{");
|
|
113
|
+
|
|
114
|
+
const result = await pruneStaleSkillDirs(skillsDir, new Set([]));
|
|
115
|
+
expect(result.pruned).toEqual([]);
|
|
116
|
+
expect(result.keptWithConsumerFiles).toHaveLength(1);
|
|
117
|
+
expect(result.keptWithConsumerFiles[0]).toContain("corrupt marker");
|
|
118
|
+
expect(existsSync(dir)).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("nested manifest files are matched recursively", async () => {
|
|
122
|
+
const dir = join(skillsDir, "nested-skill");
|
|
123
|
+
await makeSkillDir("nested-skill", ["SKILL.md", "knowledge/notes.md"]);
|
|
124
|
+
await writeSkillMarker(dir, ["SKILL.md", "knowledge/notes.md"]);
|
|
125
|
+
|
|
126
|
+
const result = await pruneStaleSkillDirs(skillsDir, new Set([]));
|
|
127
|
+
expect(result.pruned).toEqual(["nested-skill"]);
|
|
128
|
+
expect(existsSync(dir)).toBe(false);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("missing skills dir returns empty result", async () => {
|
|
132
|
+
const result = await pruneStaleSkillDirs(join(tmpDir, "nonexistent"), new Set());
|
|
133
|
+
expect(result).toEqual({ pruned: [], keptWithConsumerFiles: [] });
|
|
134
|
+
});
|