@warpgogol/forge 5.2.4 → 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 +32 -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 +43 -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 +1 -1
- package/dist/src/config/forge-config.js +1 -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.map +1 -1
- package/dist/src/onboarding/agents-generate.js +60 -31
- 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.map +1 -1
- package/dist/src/onboarding/doctor.js +68 -59
- 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.map +1 -1
- package/dist/src/onboarding/memory-compact.js +5 -4
- package/dist/src/onboarding/memory-compact.js.map +1 -1
- package/dist/src/onboarding/memory-scaffold.js +1 -1
- 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 +34 -5
- 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.js +1 -1
- 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/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 +43 -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/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 +1 -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 +74 -33
- package/src/onboarding/create.ts +10 -10
- package/src/onboarding/doctor.ts +70 -55
- package/src/onboarding/init.ts +38 -1
- package/src/onboarding/invariant-engine.ts +1 -1
- package/src/onboarding/memory-compact.ts +5 -4
- package/src/onboarding/memory-scaffold.ts +1 -1
- package/src/onboarding/nested-agents-generate.ts +45 -4
- 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 +1 -1
- 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 +6 -4
- package/src/tests/contract-registry.test.ts +12 -6
- package/src/tests/doctor-fix.test.ts +1 -4
- package/src/tests/doctor-nested-agents.test.ts +53 -1
- 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/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/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,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
|
+
});
|
|
@@ -78,14 +78,14 @@ describe("RFC-0539: Pack skill validation (SKILL-14, SKILL-15)", () => {
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
describe("RFC-0548: SKILL-16
|
|
82
|
-
test("SKILL-16: real workspace fo-skills have valid
|
|
81
|
+
describe("RFC-0548: SKILL-16 triggerPhrases field validation", () => {
|
|
82
|
+
test("SKILL-16: real workspace fo-skills have valid triggerPhrases (no violations)", () => {
|
|
83
83
|
const result = runSkillValidate({}, { workspaceRoot: process.cwd() });
|
|
84
84
|
const skill16Violations = result.violations.filter((v) => v.rule === "SKILL-16");
|
|
85
85
|
expect(skill16Violations).toEqual([]);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
|
-
test("SKILL-16:
|
|
88
|
+
test("SKILL-16: triggerPhrases are only on fo-category skills", () => {
|
|
89
89
|
const result = runSkillValidate({}, { workspaceRoot: process.cwd() });
|
|
90
90
|
const skill16Violations = result.violations.filter(
|
|
91
91
|
(v) => v.rule === "SKILL-16" && v.message.includes("only allowed on fo-category"),
|
|
@@ -93,13 +93,20 @@ describe("RFC-0548: SKILL-16 triggers field validation", () => {
|
|
|
93
93
|
expect(skill16Violations).toEqual([]);
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
test("SKILL-16: no pack skills declare
|
|
96
|
+
test("SKILL-16: no pack skills declare triggerPhrases", () => {
|
|
97
97
|
const result = runSkillValidate({}, { workspaceRoot: process.cwd() });
|
|
98
98
|
const packTriggerViolations = result.violations.filter(
|
|
99
|
-
(v) =>
|
|
99
|
+
(v) =>
|
|
100
|
+
v.rule === "SKILL-16" && v.message.includes("pack skills may not declare triggerPhrases"),
|
|
100
101
|
);
|
|
101
102
|
expect(packTriggerViolations).toEqual([]);
|
|
102
103
|
});
|
|
104
|
+
|
|
105
|
+
test("SKILL-22: no skill declares the reserved `triggers` key", () => {
|
|
106
|
+
const result = runSkillValidate({}, { workspaceRoot: process.cwd() });
|
|
107
|
+
const skill22Violations = result.violations.filter((v) => v.rule === "SKILL-22");
|
|
108
|
+
expect(skill22Violations).toEqual([]);
|
|
109
|
+
});
|
|
103
110
|
});
|
|
104
111
|
|
|
105
112
|
describe("RFC-0553: SKILL-17 platform reference prohibition", () => {
|
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { test, expect, beforeEach, afterEach, vi } from "vitest";
|
|
12
|
-
import { mkdtemp, mkdir, writeFile, rm, readFile } from "node:fs/promises";
|
|
12
|
+
import { mkdtemp, mkdir, writeFile, rm, readFile, appendFile } from "node:fs/promises";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
14
|
import { tmpdir } from "node:os";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { runUpgrade } from "../onboarding/upgrade.ts";
|
|
17
|
+
import { generateNestedAgentsMd } from "../onboarding/nested-agents-generate.ts";
|
|
18
|
+
import { ambientIo } from "../utils/io.ts";
|
|
19
|
+
import { buildGeneratedHeader } from "../utils/index.ts";
|
|
20
|
+
import type { WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io";
|
|
17
21
|
import { runInit } from "../onboarding/init.ts";
|
|
18
22
|
import {
|
|
19
23
|
loadForgeConfig,
|
|
@@ -163,7 +167,9 @@ test("forge.upgrade with syncedVersion matching installed version returns noop",
|
|
|
163
167
|
expect(result.data?.status).toBe("noop");
|
|
164
168
|
expect(result.data?.fromVersion).toBe("0.2.0");
|
|
165
169
|
expect(result.data?.toVersion).toBe("0.2.0");
|
|
166
|
-
|
|
170
|
+
// RFC-1154: the reconcile pass still runs on a same-version noop — skills it
|
|
171
|
+
// re-synced are reported in skillsUpdated.
|
|
172
|
+
expect(result.data?.skillsUpdated).toContain("fo-idea");
|
|
167
173
|
expect(result.exitCode).toBe(0);
|
|
168
174
|
});
|
|
169
175
|
|
|
@@ -358,66 +364,40 @@ test("forge.upgrade --update-npm attempts npm install for npm consumer", async (
|
|
|
358
364
|
await setupNpmConsumerForge(tempDir, "0.2.0");
|
|
359
365
|
await setupConsumerForgeYaml(tempDir, null);
|
|
360
366
|
|
|
361
|
-
//
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
exec: vi.fn(),
|
|
367
|
-
execFile: vi.fn(),
|
|
368
|
-
spawn: vi.fn(),
|
|
369
|
-
fork: vi.fn(),
|
|
370
|
-
}));
|
|
371
|
-
|
|
372
|
-
// Re-import with mock
|
|
373
|
-
const { runUpgrade: runUpgradeMocked } = await import("../onboarding/upgrade.ts");
|
|
374
|
-
|
|
375
|
-
const result = await runUpgradeMocked(
|
|
367
|
+
// RFC-1152: inject a fake WorkspaceIO — exec is the port seam; real fs stays.
|
|
368
|
+
const execMock = vi.fn(async () => ({ exitCode: 0, stdout: "", stderr: "" }));
|
|
369
|
+
const io = { ...ambientIo, exec: execMock } as WorkspaceIO;
|
|
370
|
+
|
|
371
|
+
const result = await runUpgrade(
|
|
376
372
|
{ argv: [], flags: { "update-npm": true } },
|
|
377
|
-
makeContext(tempDir, false),
|
|
373
|
+
{ ...makeContext(tempDir, false), io },
|
|
378
374
|
);
|
|
379
375
|
|
|
380
376
|
expect(result.data?.status).toBe("pass");
|
|
381
|
-
expect(
|
|
377
|
+
expect(execMock).toHaveBeenCalled();
|
|
382
378
|
expect(result.data?.npmUpdated).toBe(true);
|
|
383
379
|
expect(result.data?.npmUpdateSkipped).toBe(null);
|
|
384
|
-
|
|
385
|
-
vi.doUnmock("node:child_process");
|
|
386
|
-
vi.resetModules();
|
|
387
380
|
});
|
|
388
381
|
|
|
389
382
|
test("forge.upgrade --update-npm reports failure when npm install throws", async () => {
|
|
390
383
|
await setupNpmConsumerForge(tempDir, "0.2.0");
|
|
391
384
|
await setupConsumerForgeYaml(tempDir, null);
|
|
392
385
|
|
|
393
|
-
//
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
});
|
|
397
|
-
vi.resetModules();
|
|
398
|
-
vi.doMock("node:child_process", () => ({
|
|
399
|
-
execSync: execSyncMock,
|
|
400
|
-
exec: vi.fn(),
|
|
401
|
-
execFile: vi.fn(),
|
|
402
|
-
spawn: vi.fn(),
|
|
403
|
-
fork: vi.fn(),
|
|
404
|
-
}));
|
|
405
|
-
|
|
406
|
-
const { runUpgrade: runUpgradeMocked } = await import("../onboarding/upgrade.ts");
|
|
386
|
+
// RFC-1152: fake io.exec fails — simulates network error / registry unavailable
|
|
387
|
+
const execMock = vi.fn(async () => ({ exitCode: 1, stdout: "", stderr: "network error" }));
|
|
388
|
+
const io = { ...ambientIo, exec: execMock } as WorkspaceIO;
|
|
407
389
|
|
|
408
|
-
const result = await
|
|
390
|
+
const result = await runUpgrade(
|
|
409
391
|
{ argv: [], flags: { "update-npm": true } },
|
|
410
|
-
makeContext(tempDir, false),
|
|
392
|
+
{ ...makeContext(tempDir, false), io },
|
|
411
393
|
);
|
|
412
394
|
|
|
413
395
|
expect(result.data?.status).toBe("pass");
|
|
414
|
-
expect(
|
|
396
|
+
expect(execMock).toHaveBeenCalled();
|
|
415
397
|
expect(result.data?.npmUpdated).toBe(false);
|
|
416
398
|
expect(result.data?.npmUpdateSkipped).toBe(
|
|
417
399
|
"install failed (network error or registry unavailable)",
|
|
418
400
|
);
|
|
419
|
-
|
|
420
|
-
vi.doUnmock("node:child_process");
|
|
421
401
|
vi.resetModules();
|
|
422
402
|
});
|
|
423
403
|
|
|
@@ -432,6 +412,72 @@ test("forge.upgrade without --update-npm does not attempt npm install", async ()
|
|
|
432
412
|
expect(result.data?.npmUpdateSkipped).toBe(null);
|
|
433
413
|
});
|
|
434
414
|
|
|
415
|
+
// RFC-1153: dry-run preview of the nested-AGENTS.md merge --------------------
|
|
416
|
+
|
|
417
|
+
test("AC-7: forge.upgrade --dry-run reports nestedAgentsPlanned and nestedAgentsSkipped", async () => {
|
|
418
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
419
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
420
|
+
|
|
421
|
+
// Workspace with no AGENTS.md — merged output would be written → planned
|
|
422
|
+
await mkdir(join(tempDir, "packages", "newpkg"), { recursive: true });
|
|
423
|
+
await writeFile(join(tempDir, "packages", "newpkg", "package.json"), '{"name":"newpkg"}', "utf8");
|
|
424
|
+
|
|
425
|
+
// Marker-bearing file that diverges with no resolvable boundary → skipped
|
|
426
|
+
await mkdir(join(tempDir, "packages", "unmapped"), { recursive: true });
|
|
427
|
+
await writeFile(
|
|
428
|
+
join(tempDir, "packages", "unmapped", "package.json"),
|
|
429
|
+
'{"name":"unmapped"}',
|
|
430
|
+
"utf8",
|
|
431
|
+
);
|
|
432
|
+
await writeFile(
|
|
433
|
+
join(tempDir, "packages", "unmapped", "AGENTS.md"),
|
|
434
|
+
`${buildGeneratedHeader({ filePath: "AGENTS.md", ownerCommand: "forge.agents.generate", editable: true })}\n# Hand-maintained\n\ndivergent body\n`,
|
|
435
|
+
"utf8",
|
|
436
|
+
);
|
|
437
|
+
|
|
438
|
+
const result = await runUpgrade(
|
|
439
|
+
{ argv: [], flags: { "dry-run": true } },
|
|
440
|
+
makeContext(tempDir, true),
|
|
441
|
+
);
|
|
442
|
+
|
|
443
|
+
expect(result.data?.status).toBe("pass");
|
|
444
|
+
const planned = result.data?.nestedAgentsPlanned ?? [];
|
|
445
|
+
const skipped = result.data?.nestedAgentsSkipped ?? [];
|
|
446
|
+
|
|
447
|
+
expect(
|
|
448
|
+
planned,
|
|
449
|
+
"planned must contain exactly the guides whose merged output differs from disk",
|
|
450
|
+
).toContain(join("packages", "newpkg", "AGENTS.md"));
|
|
451
|
+
expect(skipped.some((s) => s.includes("unmapped") && s.includes("unmapped-customization"))).toBe(
|
|
452
|
+
true,
|
|
453
|
+
);
|
|
454
|
+
// No files written under dry-run
|
|
455
|
+
expect(existsSync(join(tempDir, "packages", "newpkg", "AGENTS.md"))).toBe(false);
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
test("forge.upgrade executing path reports nestedAgentsPreserved for carried tails", async () => {
|
|
459
|
+
await setupForgeSource(tempDir, "0.2.0");
|
|
460
|
+
await setupConsumerForgeYaml(tempDir, null);
|
|
461
|
+
|
|
462
|
+
// First run materializes the nested guide with the boundary marker
|
|
463
|
+
await mkdir(join(tempDir, "packages", "pkg"), { recursive: true });
|
|
464
|
+
await writeFile(join(tempDir, "packages", "pkg", "package.json"), '{"name":"pkg"}', "utf8");
|
|
465
|
+
const config = loadForgeConfig(tempDir);
|
|
466
|
+
await generateNestedAgentsMd(tempDir, config, false);
|
|
467
|
+
await appendFile(
|
|
468
|
+
join(tempDir, "packages", "pkg", "AGENTS.md"),
|
|
469
|
+
"\n## Custom rules\n\n- keep me\n",
|
|
470
|
+
"utf8",
|
|
471
|
+
);
|
|
472
|
+
|
|
473
|
+
const result = await runUpgrade({ argv: [], flags: {} }, makeContext(tempDir, false));
|
|
474
|
+
|
|
475
|
+
expect(result.data?.status).toBe("pass");
|
|
476
|
+
expect(result.data?.nestedAgentsPreserved).toContain(join("packages", "pkg", "AGENTS.md"));
|
|
477
|
+
const merged = await readFile(join(tempDir, "packages", "pkg", "AGENTS.md"), "utf8");
|
|
478
|
+
expect(merged).toContain("- keep me");
|
|
479
|
+
});
|
|
480
|
+
|
|
435
481
|
function qaLog(entries: { id: string; title: string; body: string }[]): string {
|
|
436
482
|
const blocks = entries.map(
|
|
437
483
|
(e) =>
|
package/src/types.ts
CHANGED
|
@@ -20,6 +20,101 @@ Sweep batch 4: 73 Compass headers on headerless engine files (certification, com
|
|
|
20
20
|
</CHANGE_SUMMARY>
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
+
// RFC-1152: WorkspaceIO port types — declared locally so the standalone npm
|
|
24
|
+
// build needs no @warpgogol/* package (ADR-0019). Structurally identical to
|
|
25
|
+
// @warpgogol/werkstatt-shared/kernel/workspace-io — kernel callers passing a
|
|
26
|
+
// real WorkspaceIO (context.io) are assignable both ways.
|
|
27
|
+
|
|
28
|
+
export interface ExecOptions {
|
|
29
|
+
cwd?: string;
|
|
30
|
+
timeoutMs?: number;
|
|
31
|
+
/** Extra environment variables merged over `process.env` for the child. */
|
|
32
|
+
env?: Record<string, string>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ExecResult {
|
|
36
|
+
exitCode: number | null;
|
|
37
|
+
stdout: string;
|
|
38
|
+
stderr: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* A directory entry in a port-neutral shape — no `node:fs` `Dirent` leaks
|
|
43
|
+
* through the port, so command modules never need to import `node:fs` just to
|
|
44
|
+
* type a listing. `isFile`/`isDirectory` are plain booleans (not methods).
|
|
45
|
+
*/
|
|
46
|
+
export interface DirEntry {
|
|
47
|
+
name: string;
|
|
48
|
+
isFile: boolean;
|
|
49
|
+
isDirectory: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Port-neutral `stat` result — no `node:fs` `Stats` leaks through the port. */
|
|
53
|
+
export interface FileStat {
|
|
54
|
+
isFile: boolean;
|
|
55
|
+
isDirectory: boolean;
|
|
56
|
+
mtimeMs: number;
|
|
57
|
+
size: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface WorkspaceIO {
|
|
61
|
+
readFile(path: string): Promise<string>;
|
|
62
|
+
readFileBytes(path: string): Promise<Uint8Array>;
|
|
63
|
+
exists(path: string): Promise<boolean>;
|
|
64
|
+
glob(pattern: string, opts?: { cwd?: string }): Promise<string[]>;
|
|
65
|
+
/** List a directory's immediate entries. Read-only; throws if the path is not a directory. */
|
|
66
|
+
readdir(path: string): Promise<DirEntry[]>;
|
|
67
|
+
/** File metadata in a port-neutral shape. Throws when the path does not exist. */
|
|
68
|
+
stat(path: string): Promise<FileStat>;
|
|
69
|
+
/** Resolve symlinks to the canonical absolute path. Read-only. */
|
|
70
|
+
realpath(path: string): Promise<string>;
|
|
71
|
+
/** Atomic by contract (rfc-0258). */
|
|
72
|
+
writeFile(path: string, content: string | Uint8Array): Promise<void>;
|
|
73
|
+
/** Append content to a file, creating it (and parent dirs) when absent. */
|
|
74
|
+
appendFile(path: string, content: string | Uint8Array): Promise<void>;
|
|
75
|
+
mkdir(path: string): Promise<void>;
|
|
76
|
+
rm(path: string, opts?: { recursive?: boolean }): Promise<void>;
|
|
77
|
+
rename(src: string, dst: string): Promise<void>;
|
|
78
|
+
copyFile(src: string, dst: string): Promise<void>;
|
|
79
|
+
/** Create a unique temporary directory under `prefix`; returns its path. */
|
|
80
|
+
mkdtemp(prefix: string): Promise<string>;
|
|
81
|
+
exec(command: string, args: string[], opts?: ExecOptions): Promise<ExecResult>;
|
|
82
|
+
/**
|
|
83
|
+
* RFC-1176: `true` only on the read-only adapter (`mutatesState: false`
|
|
84
|
+
* commands). Best-effort bookkeeping writes must be skipped with
|
|
85
|
+
* `if (!io.readonly)` instead of attempting a mutation that throws
|
|
86
|
+
* KERNEL-META-01 — the throw stays as the enforcement floor for
|
|
87
|
+
* undeclared mutations. Undefined on real and recording adapters.
|
|
88
|
+
*/
|
|
89
|
+
readonly readonly?: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* RFC-0960: Declared generated artifact specification on a kernel command.
|
|
94
|
+
* Lives on `ForgeCommandMetadata.generates[]` and is the source of truth
|
|
95
|
+
* for the derived generator ownership map.
|
|
96
|
+
*/
|
|
97
|
+
export interface GeneratedArtifactSpec {
|
|
98
|
+
/** Workpiece-relative path or glob, e.g. "src/content/system-health.generated.yaml". */
|
|
99
|
+
path: string;
|
|
100
|
+
/** Skip absence checks (RFC-0636 semantics) — e.g. produced only in build.post. */
|
|
101
|
+
conditional?: boolean;
|
|
102
|
+
/** Pipeline phase that produces it: informs which validator phase may assert existence. */
|
|
103
|
+
phase: "build.prepare" | "build.post" | "on-demand";
|
|
104
|
+
/**
|
|
105
|
+
* Override the default markerPolicy derivation. Default: public/** → "registry-only",
|
|
106
|
+
* everything else → "embedded". Set explicitly only for edge cases (e.g. .cache/pdf/**).
|
|
107
|
+
*/
|
|
108
|
+
markerPolicy?: "embedded" | "registry-only";
|
|
109
|
+
/**
|
|
110
|
+
* Committed bytes contain non-deterministic fields (Ed25519 proofs, real-time
|
|
111
|
+
* created/generatedAt timestamps, build durations) — byte-compare against a
|
|
112
|
+
* dryRun render can never pass. generated.drift.validate skips volatile
|
|
113
|
+
* artifacts; existence/staleness/signature checks still apply elsewhere.
|
|
114
|
+
*/
|
|
115
|
+
volatile?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
23
118
|
// ---------------------------------------------------------------------------
|
|
24
119
|
// Command input / output
|
|
25
120
|
// ---------------------------------------------------------------------------
|
|
@@ -71,6 +166,24 @@ export interface ForgeCommandMetadata {
|
|
|
71
166
|
description: string;
|
|
72
167
|
scope: ForgeCommandScope;
|
|
73
168
|
mutatesState?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* RFC-1176: permits `io.exec` on the read-only adapter — the command
|
|
171
|
+
* declares `mutatesState: false` but legitimately shells out for
|
|
172
|
+
* read-only probes. Structurally mirrors KernelCommandMetadata.
|
|
173
|
+
*/
|
|
174
|
+
execOnReadOnly?: boolean;
|
|
175
|
+
/**
|
|
176
|
+
* RFC-1176: flag names whose truthy presence upgrades this invocation to
|
|
177
|
+
* a real (or `--dry-run` recording) adapter. Structurally mirrors
|
|
178
|
+
* KernelCommandMetadata.
|
|
179
|
+
*/
|
|
180
|
+
mutatingFlags?: string[];
|
|
181
|
+
/**
|
|
182
|
+
* RFC-0960/0810: repo-relative path to the implementing module file —
|
|
183
|
+
* propagated into generator ownership entries (OWN-XCHECK-03 requires it
|
|
184
|
+
* on every command that declares generates[]).
|
|
185
|
+
*/
|
|
186
|
+
modulePath?: string;
|
|
74
187
|
requiresNetwork?: boolean;
|
|
75
188
|
supportsAllSites?: boolean;
|
|
76
189
|
/** RFC-0963: validation contract this command belongs to (e.g. "content", "seo"). */
|
|
@@ -118,6 +231,8 @@ export interface ForgeRegisteredCommandInfo extends ForgeCommandMetadata {
|
|
|
118
231
|
flags?: Record<string, ForgeFlagSpec>;
|
|
119
232
|
reads?: string[];
|
|
120
233
|
writes?: string[];
|
|
234
|
+
/** RFC-0960 generated-artifact declarations (on-demand generators). */
|
|
235
|
+
generates?: GeneratedArtifactSpec[];
|
|
121
236
|
hidden?: boolean;
|
|
122
237
|
}
|
|
123
238
|
|
|
@@ -126,6 +241,8 @@ export interface ForgeCommandDefinition<TData = unknown> extends ForgeCommandMet
|
|
|
126
241
|
flags?: Record<string, ForgeFlagSpec>;
|
|
127
242
|
reads?: string[];
|
|
128
243
|
writes?: string[];
|
|
244
|
+
/** RFC-0960 generated-artifact declarations (on-demand generators). */
|
|
245
|
+
generates?: GeneratedArtifactSpec[];
|
|
129
246
|
execute(
|
|
130
247
|
input: ForgeCommandInput,
|
|
131
248
|
context: ForgeRuntimeContext,
|
|
@@ -227,4 +344,10 @@ export interface ForgeRuntimeContext {
|
|
|
227
344
|
* use this instead of calling resolveForgeRoot(workspaceRoot).
|
|
228
345
|
*/
|
|
229
346
|
forgeRoot?: string;
|
|
347
|
+
/**
|
|
348
|
+
* RFC-1152: optional WorkspaceIO port — populated when a kernel invokes the
|
|
349
|
+
* command (writes flow through the recording adapter); absent under the
|
|
350
|
+
* standalone `forge` CLI, where helpers fall back to the ambient adapter.
|
|
351
|
+
*/
|
|
352
|
+
io?: WorkspaceIO;
|
|
230
353
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>RFC-1153: preservation-boundary split/merge for editable generated
|
|
4
|
+
files. Pure, no I/O — callers read the existing file and write the merged
|
|
5
|
+
result through WorkspaceIO. The `forge:custom` marker line bounds the
|
|
6
|
+
generator-owned head; everything below it is carried over verbatim.</purpose>
|
|
7
|
+
<non-goals>
|
|
8
|
+
<item>Do not perform file I/O — operates on in-memory content strings only.</item>
|
|
9
|
+
<item>Do not apply to non-editable generated files — GENERATED_MARKER files
|
|
10
|
+
keep the RFC-0081 binary semantics (marker present = overwrite, absent = skip).</item>
|
|
11
|
+
</non-goals>
|
|
12
|
+
</MODULE_CONTRACT>
|
|
13
|
+
<KEY_DECISIONS>
|
|
14
|
+
<item>forge:custom marker bounds the generated head; canonical footer is the fallback boundary when absent.</item>
|
|
15
|
+
</KEY_DECISIONS>
|
|
16
|
+
<CHANGE_SUMMARY>
|
|
17
|
+
<item>RFC-1153: initial — splitEditableGenerated + mergeEditableGenerated with
|
|
18
|
+
forge:custom marker boundary and canonical-footer (last non-empty render line) fallback.</item>
|
|
19
|
+
</CHANGE_SUMMARY>
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
CUSTOM_BOUNDARY_HINTS,
|
|
24
|
+
CUSTOM_BOUNDARY_MARKERS,
|
|
25
|
+
CUSTOM_BOUNDARY_TOKEN,
|
|
26
|
+
commentStyleForPath,
|
|
27
|
+
} from "./generated-marker.ts";
|
|
28
|
+
|
|
29
|
+
export interface EditableRegionSplit {
|
|
30
|
+
/** Content up to and including the boundary line (marker or footer). */
|
|
31
|
+
generatedHead: string;
|
|
32
|
+
/** Content after the boundary line, verbatim. null when empty. */
|
|
33
|
+
customTail: string | null;
|
|
34
|
+
boundary: "marker" | "footer" | "none";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** DNA-58 rule: boundary and footer matching normalize CRLF to LF. */
|
|
38
|
+
function normalizeNewlines(text: string): string {
|
|
39
|
+
return text.replace(/\r\n/g, "\n");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Canonical footer of a render — its last non-empty line. Callers that need
|
|
44
|
+
* the boundary value separately (doctor staleness, preserved-tail detection)
|
|
45
|
+
* derive it from the same render they pass to the merge.
|
|
46
|
+
*/
|
|
47
|
+
export function canonicalFooterOf(rendered: string): string | null {
|
|
48
|
+
const lines = normalizeNewlines(rendered).split("\n");
|
|
49
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
50
|
+
const line = lines[i];
|
|
51
|
+
if (line !== undefined && line.trim() !== "") return line;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Split an existing editable generated file at its preservation boundary.
|
|
58
|
+
*
|
|
59
|
+
* Priority:
|
|
60
|
+
* 1. First line containing the `forge:custom` token (comment-style agnostic —
|
|
61
|
+
* a token in prose is a documented, deliberate-looking boundary hazard).
|
|
62
|
+
* The marker line and an immediately following canonical hint line belong
|
|
63
|
+
* to the generated head.
|
|
64
|
+
* 2. `footerLine` (canonical template footer, derived by callers from the
|
|
65
|
+
* render as its last non-empty line) — first occurrence is the boundary.
|
|
66
|
+
* 3. `"none"` — no resolvable boundary.
|
|
67
|
+
*/
|
|
68
|
+
export function splitEditableGenerated(
|
|
69
|
+
content: string,
|
|
70
|
+
filePath: string,
|
|
71
|
+
footerLine?: string,
|
|
72
|
+
): EditableRegionSplit {
|
|
73
|
+
const text = normalizeNewlines(content);
|
|
74
|
+
const style = commentStyleForPath(filePath);
|
|
75
|
+
const hint = CUSTOM_BOUNDARY_HINTS[style];
|
|
76
|
+
const lines = text.split("\n");
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < lines.length; i++) {
|
|
79
|
+
const line = lines[i];
|
|
80
|
+
if (line !== undefined && line.includes(CUSTOM_BOUNDARY_TOKEN)) {
|
|
81
|
+
let headEnd = i + 1;
|
|
82
|
+
if (lines[headEnd] === hint) headEnd += 1;
|
|
83
|
+
const tail = lines.slice(headEnd).join("\n");
|
|
84
|
+
return {
|
|
85
|
+
generatedHead: lines.slice(0, headEnd).join("\n"),
|
|
86
|
+
customTail: tail.trim() === "" ? null : tail,
|
|
87
|
+
boundary: "marker",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (footerLine !== undefined) {
|
|
93
|
+
// lastIndexOf: the canonical footer is the template's last line — the
|
|
94
|
+
// trailingmost occurrence is the correct boundary even when an operator
|
|
95
|
+
// copied the line into a mid-file section.
|
|
96
|
+
const idx = lines.lastIndexOf(footerLine);
|
|
97
|
+
if (idx >= 0) {
|
|
98
|
+
const tail = lines.slice(idx + 1).join("\n");
|
|
99
|
+
return {
|
|
100
|
+
generatedHead: lines.slice(0, idx + 1).join("\n"),
|
|
101
|
+
customTail: tail.trim() === "" ? null : tail,
|
|
102
|
+
boundary: "footer",
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { generatedHead: text, customTail: null, boundary: "none" };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Merge a fresh render with an existing editable generated file.
|
|
112
|
+
*
|
|
113
|
+
* The canonical footer is derived internally as the last non-empty line of
|
|
114
|
+
* `rendered` — no per-file-kind footer registry exists; when a template tail
|
|
115
|
+
* changes, the boundary follows the new render automatically.
|
|
116
|
+
*
|
|
117
|
+
* Merge contract:
|
|
118
|
+
* - `existing === null` (new file): rendered + boundary marker + hint.
|
|
119
|
+
* - marker boundary: head = fresh render + marker + hint; tail verbatim.
|
|
120
|
+
* - footer boundary (legacy marker-less file): head = fresh render + marker +
|
|
121
|
+
* hint (marker introduced now); tail = content after the legacy footer line.
|
|
122
|
+
* - none + identical content: clean regeneration, marker + hint appended.
|
|
123
|
+
* - none + divergent content: returns null — caller must skip and warn
|
|
124
|
+
* (`unmapped-customization`), never overwrite.
|
|
125
|
+
*
|
|
126
|
+
* The result is LF-normalized; callers write through `writeFileIfChanged`.
|
|
127
|
+
*/
|
|
128
|
+
export function mergeEditableGenerated(
|
|
129
|
+
rendered: string,
|
|
130
|
+
existing: string | null,
|
|
131
|
+
filePath: string,
|
|
132
|
+
): string | null {
|
|
133
|
+
const render = normalizeNewlines(rendered);
|
|
134
|
+
const style = commentStyleForPath(filePath);
|
|
135
|
+
const marker = CUSTOM_BOUNDARY_MARKERS[style];
|
|
136
|
+
const hint = CUSTOM_BOUNDARY_HINTS[style];
|
|
137
|
+
const boundaryBlock = `${marker}\n${hint}\n`;
|
|
138
|
+
|
|
139
|
+
if (existing === null) {
|
|
140
|
+
return `${render}${boundaryBlock}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const text = normalizeNewlines(existing);
|
|
144
|
+
const footer = canonicalFooterOf(render);
|
|
145
|
+
const split = splitEditableGenerated(text, filePath, footer ?? undefined);
|
|
146
|
+
|
|
147
|
+
if (split.boundary === "marker" || split.boundary === "footer") {
|
|
148
|
+
return `${render}${boundaryBlock}${split.customTail ?? ""}`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (text === render || text === `${render}${boundaryBlock}`) {
|
|
152
|
+
return `${render}${boundaryBlock}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|