@planu/cli 0.89.0 → 0.90.1
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/dist/cli/commands/activate.d.ts +14 -0
- package/dist/cli/commands/activate.d.ts.map +1 -0
- package/dist/cli/commands/activate.js +174 -0
- package/dist/cli/commands/activate.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +16 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +162 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/install.d.ts +48 -0
- package/dist/cli/commands/install.d.ts.map +1 -0
- package/dist/cli/commands/install.js +348 -0
- package/dist/cli/commands/install.js.map +1 -0
- package/dist/cli/commands/uninstall.d.ts +10 -0
- package/dist/cli/commands/uninstall.d.ts.map +1 -0
- package/dist/cli/commands/uninstall.js +133 -0
- package/dist/cli/commands/uninstall.js.map +1 -0
- package/dist/cli/router.d.ts.map +1 -1
- package/dist/cli/router.js +9 -1
- package/dist/cli/router.js.map +1 -1
- package/dist/config/license-plans.json +2 -1
- package/dist/engine/agent-generator.test.d.ts +2 -0
- package/dist/engine/agent-generator.test.d.ts.map +1 -0
- package/dist/engine/agent-generator.test.js +556 -0
- package/dist/engine/agent-generator.test.js.map +1 -0
- package/dist/engine/analyzer.test.d.ts +2 -0
- package/dist/engine/analyzer.test.d.ts.map +1 -0
- package/dist/engine/analyzer.test.js +1461 -0
- package/dist/engine/analyzer.test.js.map +1 -0
- package/dist/engine/auditor.test.d.ts +2 -0
- package/dist/engine/auditor.test.d.ts.map +1 -0
- package/dist/engine/auditor.test.js +2075 -0
- package/dist/engine/auditor.test.js.map +1 -0
- package/dist/engine/doc-generator.test.d.ts +2 -0
- package/dist/engine/doc-generator.test.d.ts.map +1 -0
- package/dist/engine/doc-generator.test.js +961 -0
- package/dist/engine/doc-generator.test.js.map +1 -0
- package/dist/engine/estimator.test.d.ts +2 -0
- package/dist/engine/estimator.test.d.ts.map +1 -0
- package/dist/engine/estimator.test.js +334 -0
- package/dist/engine/estimator.test.js.map +1 -0
- package/dist/engine/skill-generator.test.d.ts +2 -0
- package/dist/engine/skill-generator.test.d.ts.map +1 -0
- package/dist/engine/skill-generator.test.js +742 -0
- package/dist/engine/skill-generator.test.js.map +1 -0
- package/dist/engine/spec-migrator/filesystem-import.d.ts +14 -0
- package/dist/engine/spec-migrator/filesystem-import.d.ts.map +1 -0
- package/dist/engine/spec-migrator/filesystem-import.js +96 -0
- package/dist/engine/spec-migrator/filesystem-import.js.map +1 -0
- package/dist/engine/spec-migrator/flatten-specs.d.ts +12 -0
- package/dist/engine/spec-migrator/flatten-specs.d.ts.map +1 -0
- package/dist/engine/spec-migrator/flatten-specs.js +111 -0
- package/dist/engine/spec-migrator/flatten-specs.js.map +1 -0
- package/dist/engine/spec-migrator/folder-operations.d.ts +9 -0
- package/dist/engine/spec-migrator/folder-operations.d.ts.map +1 -0
- package/dist/engine/spec-migrator/folder-operations.js +109 -0
- package/dist/engine/spec-migrator/folder-operations.js.map +1 -0
- package/dist/engine/spec-migrator/frontmatter-parser.d.ts +11 -0
- package/dist/engine/spec-migrator/frontmatter-parser.d.ts.map +1 -0
- package/dist/engine/spec-migrator/frontmatter-parser.js +92 -0
- package/dist/engine/spec-migrator/frontmatter-parser.js.map +1 -0
- package/dist/engine/spec-migrator/index.d.ts +9 -0
- package/dist/engine/spec-migrator/index.d.ts.map +1 -0
- package/dist/engine/spec-migrator/index.js +18 -0
- package/dist/engine/spec-migrator/index.js.map +1 -0
- package/dist/engine/spec-migrator/legacy-migration.d.ts +13 -0
- package/dist/engine/spec-migrator/legacy-migration.d.ts.map +1 -0
- package/dist/engine/spec-migrator/legacy-migration.js +75 -0
- package/dist/engine/spec-migrator/legacy-migration.js.map +1 -0
- package/dist/engine/spec-migrator/migration-validator.d.ts +20 -0
- package/dist/engine/spec-migrator/migration-validator.d.ts.map +1 -0
- package/dist/engine/spec-migrator/migration-validator.js +35 -0
- package/dist/engine/spec-migrator/migration-validator.js.map +1 -0
- package/dist/engine/spec-migrator/path-utils.d.ts +13 -0
- package/dist/engine/spec-migrator/path-utils.d.ts.map +1 -0
- package/dist/engine/spec-migrator/path-utils.js +40 -0
- package/dist/engine/spec-migrator/path-utils.js.map +1 -0
- package/dist/engine/spec-migrator/prefix-migration.d.ts +11 -0
- package/dist/engine/spec-migrator/prefix-migration.d.ts.map +1 -0
- package/dist/engine/spec-migrator/prefix-migration.js +73 -0
- package/dist/engine/spec-migrator/prefix-migration.js.map +1 -0
- package/dist/engine/spec-migrator/reconcile-paths.d.ts +12 -0
- package/dist/engine/spec-migrator/reconcile-paths.d.ts.map +1 -0
- package/dist/engine/spec-migrator/reconcile-paths.js +77 -0
- package/dist/engine/spec-migrator/reconcile-paths.js.map +1 -0
- package/dist/engine/spec-migrator/version-detection.d.ts +5 -0
- package/dist/engine/spec-migrator/version-detection.d.ts.map +1 -0
- package/dist/engine/spec-migrator/version-detection.js +19 -0
- package/dist/engine/spec-migrator/version-detection.js.map +1 -0
- package/dist/engine/spec-migrator.d.ts +1 -58
- package/dist/engine/spec-migrator.d.ts.map +1 -1
- package/dist/engine/spec-migrator.js +2 -658
- package/dist/engine/spec-migrator.js.map +1 -1
- package/dist/engine/spec-summary-html/dashboard-renderer.d.ts +6 -0
- package/dist/engine/spec-summary-html/dashboard-renderer.d.ts.map +1 -0
- package/dist/engine/spec-summary-html/dashboard-renderer.js +333 -0
- package/dist/engine/spec-summary-html/dashboard-renderer.js.map +1 -0
- package/dist/engine/spec-summary-html/hash-utils.d.ts +11 -0
- package/dist/engine/spec-summary-html/hash-utils.d.ts.map +1 -0
- package/dist/engine/spec-summary-html/hash-utils.js +39 -0
- package/dist/engine/spec-summary-html/hash-utils.js.map +1 -0
- package/dist/engine/spec-summary-html/index.d.ts +4 -0
- package/dist/engine/spec-summary-html/index.d.ts.map +1 -0
- package/dist/engine/spec-summary-html/index.js +6 -0
- package/dist/engine/spec-summary-html/index.js.map +1 -0
- package/dist/engine/spec-summary-html/report-renderer.d.ts +9 -0
- package/dist/engine/spec-summary-html/report-renderer.d.ts.map +1 -0
- package/dist/engine/spec-summary-html/report-renderer.js +139 -0
- package/dist/engine/spec-summary-html/report-renderer.js.map +1 -0
- package/dist/engine/spec-summary-html.d.ts +1 -0
- package/dist/engine/spec-summary-html.d.ts.map +1 -1
- package/dist/engine/spec-summary-html.js +19 -473
- package/dist/engine/spec-summary-html.js.map +1 -1
- package/dist/engine/update-notifier.d.ts +8 -0
- package/dist/engine/update-notifier.d.ts.map +1 -0
- package/dist/engine/update-notifier.js +130 -0
- package/dist/engine/update-notifier.js.map +1 -0
- package/dist/engine/validator/dor-dod.d.ts.map +1 -1
- package/dist/engine/validator/dor-dod.js +8 -5
- package/dist/engine/validator/dor-dod.js.map +1 -1
- package/dist/engine/validator.d.ts.map +1 -1
- package/dist/engine/validator.js +4 -3
- package/dist/engine/validator.js.map +1 -1
- package/dist/engine/validator.test.d.ts +2 -0
- package/dist/engine/validator.test.d.ts.map +1 -0
- package/dist/engine/validator.test.js +2371 -0
- package/dist/engine/validator.test.js.map +1 -0
- package/dist/engine/web-fetcher.test.d.ts +2 -0
- package/dist/engine/web-fetcher.test.d.ts.map +1 -0
- package/dist/engine/web-fetcher.test.js +360 -0
- package/dist/engine/web-fetcher.test.js.map +1 -0
- package/dist/i18n/index.test.d.ts +2 -0
- package/dist/i18n/index.test.d.ts.map +1 -0
- package/dist/i18n/index.test.js +375 -0
- package/dist/i18n/index.test.js.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.d.ts +2 -0
- package/dist/index.test.d.ts.map +1 -0
- package/dist/index.test.js +124 -0
- package/dist/index.test.js.map +1 -0
- package/dist/resources/patterns.test.d.ts +2 -0
- package/dist/resources/patterns.test.d.ts.map +1 -0
- package/dist/resources/patterns.test.js +142 -0
- package/dist/resources/patterns.test.js.map +1 -0
- package/dist/resources/process.test.d.ts +2 -0
- package/dist/resources/process.test.d.ts.map +1 -0
- package/dist/resources/process.test.js +48 -0
- package/dist/resources/process.test.js.map +1 -0
- package/dist/resources/registry.test.d.ts +2 -0
- package/dist/resources/registry.test.d.ts.map +1 -0
- package/dist/resources/registry.test.js +138 -0
- package/dist/resources/registry.test.js.map +1 -0
- package/dist/resources/specs.test.d.ts +2 -0
- package/dist/resources/specs.test.d.ts.map +1 -0
- package/dist/resources/specs.test.js +130 -0
- package/dist/resources/specs.test.js.map +1 -0
- package/dist/resources/templates.test.d.ts +2 -0
- package/dist/resources/templates.test.d.ts.map +1 -0
- package/dist/resources/templates.test.js +119 -0
- package/dist/resources/templates.test.js.map +1 -0
- package/dist/smoke.test.d.ts +2 -0
- package/dist/smoke.test.d.ts.map +1 -0
- package/dist/smoke.test.js +229 -0
- package/dist/smoke.test.js.map +1 -0
- package/dist/storage/base-store.test.d.ts +2 -0
- package/dist/storage/base-store.test.d.ts.map +1 -0
- package/dist/storage/base-store.test.js +180 -0
- package/dist/storage/base-store.test.js.map +1 -0
- package/dist/storage/global-store.test.d.ts +2 -0
- package/dist/storage/global-store.test.d.ts.map +1 -0
- package/dist/storage/global-store.test.js +327 -0
- package/dist/storage/global-store.test.js.map +1 -0
- package/dist/storage/index.test.d.ts +2 -0
- package/dist/storage/index.test.d.ts.map +1 -0
- package/dist/storage/index.test.js +56 -0
- package/dist/storage/index.test.js.map +1 -0
- package/dist/storage/knowledge-store.test.d.ts +2 -0
- package/dist/storage/knowledge-store.test.d.ts.map +1 -0
- package/dist/storage/knowledge-store.test.js +368 -0
- package/dist/storage/knowledge-store.test.js.map +1 -0
- package/dist/storage/metrics-store.test.d.ts +2 -0
- package/dist/storage/metrics-store.test.d.ts.map +1 -0
- package/dist/storage/metrics-store.test.js +212 -0
- package/dist/storage/metrics-store.test.js.map +1 -0
- package/dist/storage/pattern-store.test.d.ts +2 -0
- package/dist/storage/pattern-store.test.d.ts.map +1 -0
- package/dist/storage/pattern-store.test.js +224 -0
- package/dist/storage/pattern-store.test.js.map +1 -0
- package/dist/storage/spec-store.test.d.ts +2 -0
- package/dist/storage/spec-store.test.d.ts.map +1 -0
- package/dist/storage/spec-store.test.js +227 -0
- package/dist/storage/spec-store.test.js.map +1 -0
- package/dist/tools/audit.test.d.ts +2 -0
- package/dist/tools/audit.test.d.ts.map +1 -0
- package/dist/tools/audit.test.js +169 -0
- package/dist/tools/audit.test.js.map +1 -0
- package/dist/tools/challenge-spec.test.d.ts +2 -0
- package/dist/tools/challenge-spec.test.d.ts.map +1 -0
- package/dist/tools/challenge-spec.test.js +782 -0
- package/dist/tools/challenge-spec.test.js.map +1 -0
- package/dist/tools/check-versions.test.d.ts +2 -0
- package/dist/tools/check-versions.test.d.ts.map +1 -0
- package/dist/tools/check-versions.test.js +214 -0
- package/dist/tools/check-versions.test.js.map +1 -0
- package/dist/tools/clarify-requirements.test.d.ts +2 -0
- package/dist/tools/clarify-requirements.test.d.ts.map +1 -0
- package/dist/tools/clarify-requirements.test.js +161 -0
- package/dist/tools/clarify-requirements.test.js.map +1 -0
- package/dist/tools/consult-docs.test.d.ts +2 -0
- package/dist/tools/consult-docs.test.d.ts.map +1 -0
- package/dist/tools/consult-docs.test.js +140 -0
- package/dist/tools/consult-docs.test.js.map +1 -0
- package/dist/tools/create-spec.test.d.ts +2 -0
- package/dist/tools/create-spec.test.d.ts.map +1 -0
- package/dist/tools/create-spec.test.js +233 -0
- package/dist/tools/create-spec.test.js.map +1 -0
- package/dist/tools/define-ui-contract.test.d.ts +2 -0
- package/dist/tools/define-ui-contract.test.d.ts.map +1 -0
- package/dist/tools/define-ui-contract.test.js +479 -0
- package/dist/tools/define-ui-contract.test.js.map +1 -0
- package/dist/tools/design-schema.test.d.ts +2 -0
- package/dist/tools/design-schema.test.d.ts.map +1 -0
- package/dist/tools/design-schema.test.js +301 -0
- package/dist/tools/design-schema.test.js.map +1 -0
- package/dist/tools/detect-agent.test.d.ts +2 -0
- package/dist/tools/detect-agent.test.d.ts.map +1 -0
- package/dist/tools/detect-agent.test.js +133 -0
- package/dist/tools/detect-agent.test.js.map +1 -0
- package/dist/tools/detect-drift.test.d.ts +2 -0
- package/dist/tools/detect-drift.test.d.ts.map +1 -0
- package/dist/tools/detect-drift.test.js +312 -0
- package/dist/tools/detect-drift.test.js.map +1 -0
- package/dist/tools/discover-mcps.test.d.ts +2 -0
- package/dist/tools/discover-mcps.test.d.ts.map +1 -0
- package/dist/tools/discover-mcps.test.js +345 -0
- package/dist/tools/discover-mcps.test.js.map +1 -0
- package/dist/tools/estimate.test.d.ts +2 -0
- package/dist/tools/estimate.test.d.ts.map +1 -0
- package/dist/tools/estimate.test.js +137 -0
- package/dist/tools/estimate.test.js.map +1 -0
- package/dist/tools/generate-adr.test.d.ts +2 -0
- package/dist/tools/generate-adr.test.d.ts.map +1 -0
- package/dist/tools/generate-adr.test.js +206 -0
- package/dist/tools/generate-adr.test.js.map +1 -0
- package/dist/tools/generate-checklist.test.d.ts +2 -0
- package/dist/tools/generate-checklist.test.d.ts.map +1 -0
- package/dist/tools/generate-checklist.test.js +201 -0
- package/dist/tools/generate-checklist.test.js.map +1 -0
- package/dist/tools/generate-docs.test.d.ts +2 -0
- package/dist/tools/generate-docs.test.d.ts.map +1 -0
- package/dist/tools/generate-docs.test.js +183 -0
- package/dist/tools/generate-docs.test.js.map +1 -0
- package/dist/tools/generate-execution-plan.test.d.ts +2 -0
- package/dist/tools/generate-execution-plan.test.d.ts.map +1 -0
- package/dist/tools/generate-execution-plan.test.js +643 -0
- package/dist/tools/generate-execution-plan.test.js.map +1 -0
- package/dist/tools/generate-rules.test.d.ts +2 -0
- package/dist/tools/generate-rules.test.d.ts.map +1 -0
- package/dist/tools/generate-rules.test.js +148 -0
- package/dist/tools/generate-rules.test.js.map +1 -0
- package/dist/tools/generate-skill.test.d.ts +2 -0
- package/dist/tools/generate-skill.test.d.ts.map +1 -0
- package/dist/tools/generate-skill.test.js +138 -0
- package/dist/tools/generate-skill.test.js.map +1 -0
- package/dist/tools/generate-sub-agent.test.d.ts +2 -0
- package/dist/tools/generate-sub-agent.test.d.ts.map +1 -0
- package/dist/tools/generate-sub-agent.test.js +162 -0
- package/dist/tools/generate-sub-agent.test.js.map +1 -0
- package/dist/tools/generate-tests.test.d.ts +2 -0
- package/dist/tools/generate-tests.test.d.ts.map +1 -0
- package/dist/tools/generate-tests.test.js +222 -0
- package/dist/tools/generate-tests.test.js.map +1 -0
- package/dist/tools/init-constitution.test.d.ts +2 -0
- package/dist/tools/init-constitution.test.d.ts.map +1 -0
- package/dist/tools/init-constitution.test.js +398 -0
- package/dist/tools/init-constitution.test.js.map +1 -0
- package/dist/tools/init-project/config-builder.d.ts +12 -0
- package/dist/tools/init-project/config-builder.d.ts.map +1 -0
- package/dist/tools/init-project/config-builder.js +31 -0
- package/dist/tools/init-project/config-builder.js.map +1 -0
- package/dist/tools/init-project/git-setup.d.ts +8 -0
- package/dist/tools/init-project/git-setup.d.ts.map +1 -0
- package/dist/tools/init-project/git-setup.js +70 -0
- package/dist/tools/init-project/git-setup.js.map +1 -0
- package/dist/tools/init-project/handler.d.ts.map +1 -1
- package/dist/tools/init-project/handler.js +25 -371
- package/dist/tools/init-project/handler.js.map +1 -1
- package/dist/tools/init-project/lifecycle-helpers.d.ts +32 -0
- package/dist/tools/init-project/lifecycle-helpers.d.ts.map +1 -0
- package/dist/tools/init-project/lifecycle-helpers.js +153 -0
- package/dist/tools/init-project/lifecycle-helpers.js.map +1 -0
- package/dist/tools/init-project/migration-runner.d.ts +28 -0
- package/dist/tools/init-project/migration-runner.d.ts.map +1 -0
- package/dist/tools/init-project/migration-runner.js +57 -0
- package/dist/tools/init-project/migration-runner.js.map +1 -0
- package/dist/tools/init-project/rules-writer.d.ts +14 -0
- package/dist/tools/init-project/rules-writer.d.ts.map +1 -0
- package/dist/tools/init-project/rules-writer.js +43 -0
- package/dist/tools/init-project/rules-writer.js.map +1 -0
- package/dist/tools/init-project/scaffold-writer.d.ts +29 -0
- package/dist/tools/init-project/scaffold-writer.d.ts.map +1 -0
- package/dist/tools/init-project/scaffold-writer.js +76 -0
- package/dist/tools/init-project/scaffold-writer.js.map +1 -0
- package/dist/tools/init-project/stack-detector.d.ts +16 -0
- package/dist/tools/init-project/stack-detector.d.ts.map +1 -0
- package/dist/tools/init-project/stack-detector.js +19 -0
- package/dist/tools/init-project/stack-detector.js.map +1 -0
- package/dist/tools/init-project.test.d.ts +2 -0
- package/dist/tools/init-project.test.d.ts.map +1 -0
- package/dist/tools/init-project.test.js +158 -0
- package/dist/tools/init-project.test.js.map +1 -0
- package/dist/tools/integrate-pm.test.d.ts +2 -0
- package/dist/tools/integrate-pm.test.d.ts.map +1 -0
- package/dist/tools/integrate-pm.test.js +558 -0
- package/dist/tools/integrate-pm.test.js.map +1 -0
- package/dist/tools/learn.test.d.ts +2 -0
- package/dist/tools/learn.test.d.ts.map +1 -0
- package/dist/tools/learn.test.js +123 -0
- package/dist/tools/learn.test.js.map +1 -0
- package/dist/tools/list-specs.js +1 -1
- package/dist/tools/list-specs.js.map +1 -1
- package/dist/tools/list-specs.test.d.ts +2 -0
- package/dist/tools/list-specs.test.d.ts.map +1 -0
- package/dist/tools/list-specs.test.js +110 -0
- package/dist/tools/list-specs.test.js.map +1 -0
- package/dist/tools/manage-context.test.d.ts +2 -0
- package/dist/tools/manage-context.test.d.ts.map +1 -0
- package/dist/tools/manage-context.test.js +359 -0
- package/dist/tools/manage-context.test.js.map +1 -0
- package/dist/tools/manage-git.test.d.ts +2 -0
- package/dist/tools/manage-git.test.d.ts.map +1 -0
- package/dist/tools/manage-git.test.js +882 -0
- package/dist/tools/manage-git.test.js.map +1 -0
- package/dist/tools/orchestrate.test.d.ts +2 -0
- package/dist/tools/orchestrate.test.d.ts.map +1 -0
- package/dist/tools/orchestrate.test.js +1117 -0
- package/dist/tools/orchestrate.test.js.map +1 -0
- package/dist/tools/reconcile-spec.test.d.ts +2 -0
- package/dist/tools/reconcile-spec.test.d.ts.map +1 -0
- package/dist/tools/reconcile-spec.test.js +259 -0
- package/dist/tools/reconcile-spec.test.js.map +1 -0
- package/dist/tools/red-team.d.ts +3 -0
- package/dist/tools/red-team.d.ts.map +1 -0
- package/dist/tools/red-team.js +302 -0
- package/dist/tools/red-team.js.map +1 -0
- package/dist/tools/register-platform-tools/design-stack-tools.d.ts.map +1 -1
- package/dist/tools/register-platform-tools/design-stack-tools.js +14 -0
- package/dist/tools/register-platform-tools/design-stack-tools.js.map +1 -1
- package/dist/tools/register-platform-tools.test.d.ts +2 -0
- package/dist/tools/register-platform-tools.test.d.ts.map +1 -0
- package/dist/tools/register-platform-tools.test.js +404 -0
- package/dist/tools/register-platform-tools.test.js.map +1 -0
- package/dist/tools/register-spec-tools.test.d.ts +2 -0
- package/dist/tools/register-spec-tools.test.d.ts.map +1 -0
- package/dist/tools/register-spec-tools.test.js +407 -0
- package/dist/tools/register-spec-tools.test.js.map +1 -0
- package/dist/tools/reverse-engineer.test.d.ts +2 -0
- package/dist/tools/reverse-engineer.test.d.ts.map +1 -0
- package/dist/tools/reverse-engineer.test.js +206 -0
- package/dist/tools/reverse-engineer.test.js.map +1 -0
- package/dist/tools/schemas.d.ts +20 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +133 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools/schemas.test.d.ts +2 -0
- package/dist/tools/schemas.test.d.ts.map +1 -0
- package/dist/tools/schemas.test.js +245 -0
- package/dist/tools/schemas.test.js.map +1 -0
- package/dist/tools/set-locale.test.d.ts +2 -0
- package/dist/tools/set-locale.test.d.ts.map +1 -0
- package/dist/tools/set-locale.test.js +74 -0
- package/dist/tools/set-locale.test.js.map +1 -0
- package/dist/tools/suggest-mcps.test.d.ts +2 -0
- package/dist/tools/suggest-mcps.test.d.ts.map +1 -0
- package/dist/tools/suggest-mcps.test.js +198 -0
- package/dist/tools/suggest-mcps.test.js.map +1 -0
- package/dist/tools/suggest-stack.test.d.ts +2 -0
- package/dist/tools/suggest-stack.test.d.ts.map +1 -0
- package/dist/tools/suggest-stack.test.js +181 -0
- package/dist/tools/suggest-stack.test.js.map +1 -0
- package/dist/tools/suggest-tooling.test.d.ts +2 -0
- package/dist/tools/suggest-tooling.test.d.ts.map +1 -0
- package/dist/tools/suggest-tooling.test.js +213 -0
- package/dist/tools/suggest-tooling.test.js.map +1 -0
- package/dist/tools/summarize-spec.test.d.ts +2 -0
- package/dist/tools/summarize-spec.test.d.ts.map +1 -0
- package/dist/tools/summarize-spec.test.js +180 -0
- package/dist/tools/summarize-spec.test.js.map +1 -0
- package/dist/tools/update-status/dod-gates.d.ts +16 -0
- package/dist/tools/update-status/dod-gates.d.ts.map +1 -0
- package/dist/tools/update-status/dod-gates.js +117 -0
- package/dist/tools/update-status/dod-gates.js.map +1 -0
- package/dist/tools/update-status/file-sync.d.ts +6 -0
- package/dist/tools/update-status/file-sync.d.ts.map +1 -0
- package/dist/tools/update-status/file-sync.js +112 -0
- package/dist/tools/update-status/file-sync.js.map +1 -0
- package/dist/tools/update-status/index.d.ts +3 -0
- package/dist/tools/update-status/index.d.ts.map +1 -0
- package/dist/tools/update-status/index.js +181 -0
- package/dist/tools/update-status/index.js.map +1 -0
- package/dist/tools/update-status/response-builder.d.ts +4 -0
- package/dist/tools/update-status/response-builder.d.ts.map +1 -0
- package/dist/tools/update-status/response-builder.js +69 -0
- package/dist/tools/update-status/response-builder.js.map +1 -0
- package/dist/tools/update-status/side-effects.d.ts +15 -0
- package/dist/tools/update-status/side-effects.d.ts.map +1 -0
- package/dist/tools/update-status/side-effects.js +64 -0
- package/dist/tools/update-status/side-effects.js.map +1 -0
- package/dist/tools/update-status/transition-guard.d.ts +20 -0
- package/dist/tools/update-status/transition-guard.d.ts.map +1 -0
- package/dist/tools/update-status/transition-guard.js +75 -0
- package/dist/tools/update-status/transition-guard.js.map +1 -0
- package/dist/tools/update-status.d.ts +1 -2
- package/dist/tools/update-status.d.ts.map +1 -1
- package/dist/tools/update-status.js +2 -481
- package/dist/tools/update-status.js.map +1 -1
- package/dist/tools/update-status.test.d.ts +2 -0
- package/dist/tools/update-status.test.d.ts.map +1 -0
- package/dist/tools/update-status.test.js +142 -0
- package/dist/tools/update-status.test.js.map +1 -0
- package/dist/tools/validate.d.ts.map +1 -1
- package/dist/tools/validate.js +6 -4
- package/dist/tools/validate.js.map +1 -1
- package/dist/tools/validate.test.d.ts +2 -0
- package/dist/tools/validate.test.d.ts.map +1 -0
- package/dist/tools/validate.test.js +137 -0
- package/dist/tools/validate.test.js.map +1 -0
- package/dist/types/analysis.d.ts +2 -1
- package/dist/types/analysis.d.ts.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/red-team.d.ts +29 -0
- package/dist/types/red-team.d.ts.map +1 -0
- package/dist/types/red-team.js +3 -0
- package/dist/types/red-team.js.map +1 -0
- package/dist/types/update-notifier.d.ts +5 -0
- package/dist/types/update-notifier.d.ts.map +1 -0
- package/dist/types/update-notifier.js +3 -0
- package/dist/types/update-notifier.js.map +1 -0
- package/package.json +9 -2
- package/src/config/license-plans.json +2 -1
- package/src/i18n/messages/en.json +5 -0
- package/src/i18n/messages/es.json +5 -0
- package/src/i18n/messages/pt.json +5 -0
|
@@ -1,659 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { join, dirname } from 'node:path';
|
|
4
|
-
const LEGACY_TO_MODERN_NAMES = {
|
|
5
|
-
'HU.md': 'spec.md',
|
|
6
|
-
'FICHA-TECNICA.md': 'technical.md',
|
|
7
|
-
'PROGRESS.md': 'progress.md',
|
|
8
|
-
};
|
|
9
|
-
/**
|
|
10
|
-
* Detect specs stored under a legacy location (docs/sdd/specs/).
|
|
11
|
-
* Caller provides the list of specs (from storage layer).
|
|
12
|
-
*/
|
|
13
|
-
export function filterLegacySpecs(specs) {
|
|
14
|
-
return specs.filter((s) => isLegacyLocation(s.specPath) || isLegacyLocation(s.technicalPath));
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Migrate legacy specs to planu/specs/ with modern file naming.
|
|
18
|
-
* Moves files on disk and calls the provided updateSpec for each migrated spec.
|
|
19
|
-
* Also rewrites planu.json.
|
|
20
|
-
*/
|
|
21
|
-
export async function migrateSpecs(projectPath, legacySpecs, deps) {
|
|
22
|
-
const errors = [];
|
|
23
|
-
let migratedCount = 0;
|
|
24
|
-
for (const spec of legacySpecs) {
|
|
25
|
-
try {
|
|
26
|
-
await migrateSingleSpec(projectPath, spec, deps);
|
|
27
|
-
migratedCount++;
|
|
28
|
-
}
|
|
29
|
-
catch (err) {
|
|
30
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
31
|
-
errors.push(`${spec.id}: ${msg}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
// Rewrite planu.json with modern config
|
|
35
|
-
const planuConfigPath = join(projectPath, 'planu.json');
|
|
36
|
-
const planuConfig = createPlanuConfig();
|
|
37
|
-
await writeFile(planuConfigPath, serializePlanuConfig(planuConfig), 'utf-8');
|
|
38
|
-
return { migratedCount, errors };
|
|
39
|
-
}
|
|
40
|
-
async function migrateSingleSpec(projectPath, spec, deps) {
|
|
41
|
-
const oldSpecDir = dirname(join(projectPath, spec.specPath));
|
|
42
|
-
// Flatten: remove any subcategory between planu/specs/ and the spec folder.
|
|
43
|
-
// Legacy: docs/sdd/specs/{category}/{slug}/spec.md -> planu/specs/{slug}/spec.md
|
|
44
|
-
const newRelDir = flattenSpecPath(spec.specPath.replace(/docs\/sdd\/specs/, 'planu/specs').replace(/\/[^/]+$/, ''));
|
|
45
|
-
const newAbsDir = join(projectPath, newRelDir);
|
|
46
|
-
// Create target directory
|
|
47
|
-
await mkdir(newAbsDir, { recursive: true });
|
|
48
|
-
// Move and rename known files
|
|
49
|
-
const dirEntries = await safeReaddir(oldSpecDir);
|
|
50
|
-
for (const entry of dirEntries) {
|
|
51
|
-
const modernName = LEGACY_TO_MODERN_NAMES[entry] ?? entry;
|
|
52
|
-
const src = join(oldSpecDir, entry);
|
|
53
|
-
const dest = join(newAbsDir, modernName);
|
|
54
|
-
await rename(src, dest);
|
|
55
|
-
}
|
|
56
|
-
// Update spec paths in storage
|
|
57
|
-
const updates = {
|
|
58
|
-
specPath: rewritePath(spec.specPath),
|
|
59
|
-
technicalPath: rewritePath(spec.technicalPath),
|
|
60
|
-
};
|
|
61
|
-
if (spec.progressPath) {
|
|
62
|
-
updates.progressPath = rewritePath(spec.progressPath);
|
|
63
|
-
}
|
|
64
|
-
if (spec.executiveReportPath) {
|
|
65
|
-
updates.executiveReportPath = rewritePath(spec.executiveReportPath);
|
|
66
|
-
}
|
|
67
|
-
if (spec.technicalReportPath) {
|
|
68
|
-
updates.technicalReportPath = rewritePath(spec.technicalReportPath);
|
|
69
|
-
}
|
|
70
|
-
if (spec.planPath) {
|
|
71
|
-
updates.planPath = rewritePath(spec.planPath);
|
|
72
|
-
}
|
|
73
|
-
await deps.updateSpec(spec.projectId, spec.id, updates);
|
|
74
|
-
// Cleanup empty legacy directories
|
|
75
|
-
await cleanupEmptyDirs(oldSpecDir);
|
|
76
|
-
}
|
|
77
|
-
function rewritePath(path) {
|
|
78
|
-
const modernized = path
|
|
79
|
-
.replace(/docs\/sdd\/specs/, 'planu/specs')
|
|
80
|
-
.replace(/\/HU\.md$/, '/spec.md')
|
|
81
|
-
.replace(/\/FICHA-TECNICA\.md$/, '/technical.md')
|
|
82
|
-
.replace(/\/PROGRESS\.md$/, '/progress.md');
|
|
83
|
-
return flattenSpecPath(modernized);
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Flatten nested spec paths to the new flat structure.
|
|
87
|
-
* Converts `planu/specs/{category}/{slug}/file.md` to `planu/specs/{slug}/file.md`.
|
|
88
|
-
* Already-flat paths are returned unchanged.
|
|
89
|
-
*/
|
|
90
|
-
function flattenSpecPath(p) {
|
|
91
|
-
// Match: planu/specs/{category}/{specFolder}[/rest]
|
|
92
|
-
// A spec folder starts with SPEC- or is the direct slug folder.
|
|
93
|
-
// We detect nesting by counting segments after planu/specs/.
|
|
94
|
-
const match = /^(planu\/specs\/)([^/]+)\/([^/]+)(\/.*)?$/.exec(p);
|
|
95
|
-
if (!match) {
|
|
96
|
-
return p;
|
|
97
|
-
}
|
|
98
|
-
const [, prefix, segment1, segment2, rest] = match;
|
|
99
|
-
// If segment1 is already a spec folder (starts with SPEC-), it's flat — no change.
|
|
100
|
-
if (segment1?.startsWith('SPEC-')) {
|
|
101
|
-
return p;
|
|
102
|
-
}
|
|
103
|
-
// segment1 is a subcategory — remove it, promote segment2 to direct child.
|
|
104
|
-
return `${prefix}${segment2}${rest ?? ''}`;
|
|
105
|
-
}
|
|
106
|
-
async function safeReaddir(dir) {
|
|
107
|
-
try {
|
|
108
|
-
return await readdir(dir);
|
|
109
|
-
}
|
|
110
|
-
catch {
|
|
111
|
-
return [];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
async function cleanupEmptyDirs(dir) {
|
|
115
|
-
try {
|
|
116
|
-
const entries = await readdir(dir);
|
|
117
|
-
if (entries.length === 0) {
|
|
118
|
-
await rmdir(dir);
|
|
119
|
-
// Try to clean parent too (e.g., docs/sdd/specs/ itself)
|
|
120
|
-
await cleanupEmptyDirs(dirname(dir));
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
catch {
|
|
124
|
-
// Directory might not exist or not be empty — safe to ignore
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// --- Auto-import filesystem specs into store ---
|
|
128
|
-
const SPEC_FOLDER_ID_RE = /^(SPEC-\d{3,})/;
|
|
129
|
-
/**
|
|
130
|
-
* Import specs that exist on disk (planu/specs/) but are missing from the store.
|
|
131
|
-
* Reads frontmatter from each spec.md and creates a minimal Spec record in the store.
|
|
132
|
-
* This ensures list_specs returns ALL specs, not just those created via create_spec.
|
|
133
|
-
*/
|
|
134
|
-
export async function importFilesystemSpecs(projectPath, deps, projectId) {
|
|
135
|
-
const specsRoot = join(projectPath, 'planu/specs');
|
|
136
|
-
const errors = [];
|
|
137
|
-
let importedCount = 0;
|
|
138
|
-
// Load existing store IDs for fast lookup
|
|
139
|
-
const storeSpecs = await deps.listSpecs(projectId);
|
|
140
|
-
const storeIds = new Set(storeSpecs.map((s) => s.id));
|
|
141
|
-
// Scan filesystem
|
|
142
|
-
let entries;
|
|
143
|
-
try {
|
|
144
|
-
entries = await readdir(specsRoot, { withFileTypes: true });
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
return { importedCount: 0, errors: [] };
|
|
148
|
-
}
|
|
149
|
-
for (const entry of entries) {
|
|
150
|
-
if (!entry.isDirectory()) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
// Extract SPEC ID from folder name (e.g. SPEC-001-sdd-mcp-server → SPEC-001)
|
|
154
|
-
const idMatch = SPEC_FOLDER_ID_RE.exec(entry.name);
|
|
155
|
-
if (!idMatch?.[1]) {
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
const specId = idMatch[1];
|
|
159
|
-
// Skip if already in store
|
|
160
|
-
if (storeIds.has(specId)) {
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
// Read and parse frontmatter
|
|
164
|
-
const specMdPath = join(specsRoot, entry.name, 'spec.md');
|
|
165
|
-
try {
|
|
166
|
-
const content = await readFile(specMdPath, 'utf-8');
|
|
167
|
-
const { metadata } = parseFrontmatterLocal(content);
|
|
168
|
-
const now = new Date().toISOString();
|
|
169
|
-
const folder = `planu/specs/${entry.name}`;
|
|
170
|
-
const slug = entry.name.replace(/^SPEC-\d{3,}-/, '');
|
|
171
|
-
const spec = {
|
|
172
|
-
id: specId,
|
|
173
|
-
title: typeof metadata.title === 'string' ? metadata.title : specId,
|
|
174
|
-
slug,
|
|
175
|
-
type: validSpecType(metadata.type) ?? 'feature',
|
|
176
|
-
scope: validSpecScope(metadata.scope) ?? 'feature',
|
|
177
|
-
status: validSpecStatus(metadata.status) ?? 'draft',
|
|
178
|
-
difficulty: validDifficulty(metadata.difficulty) ?? 3,
|
|
179
|
-
risk: validRiskLevel(metadata.risk) ?? 'medium',
|
|
180
|
-
target: validSpecTarget(metadata.target) ?? 'backend',
|
|
181
|
-
projectId,
|
|
182
|
-
createdAt: typeof metadata.created === 'string' ? new Date(metadata.created).toISOString() : now,
|
|
183
|
-
updatedAt: now,
|
|
184
|
-
specPath: `${folder}/spec.md`,
|
|
185
|
-
technicalPath: `${folder}/technical.md`,
|
|
186
|
-
progressPath: `${folder}/progress.md`,
|
|
187
|
-
estimation: {
|
|
188
|
-
devHours: 0,
|
|
189
|
-
reviewHours: 0,
|
|
190
|
-
recommendedModel: 'sonnet',
|
|
191
|
-
tokensOpus: 0,
|
|
192
|
-
tokensSonnet: 0,
|
|
193
|
-
apiCostUsd: 0,
|
|
194
|
-
hourlyRate: 0,
|
|
195
|
-
humanCostUsd: 0,
|
|
196
|
-
totalCostUsd: 0,
|
|
197
|
-
tokenOptimization: {
|
|
198
|
-
mode: 'local',
|
|
199
|
-
reasoning: 'imported from filesystem',
|
|
200
|
-
estimatedTokens: 0,
|
|
201
|
-
savings: '',
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
actuals: null,
|
|
205
|
-
tags: Array.isArray(metadata.tags) ? metadata.tags : [],
|
|
206
|
-
dependencies: [],
|
|
207
|
-
blockedBy: [],
|
|
208
|
-
gitBranch: `feat/${specId.toLowerCase()}-${slug}`,
|
|
209
|
-
impactAnalysis: null,
|
|
210
|
-
};
|
|
211
|
-
await deps.createSpec(projectId, spec);
|
|
212
|
-
storeIds.add(specId);
|
|
213
|
-
importedCount++;
|
|
214
|
-
}
|
|
215
|
-
catch (err) {
|
|
216
|
-
errors.push(`${specId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
return { importedCount, errors };
|
|
220
|
-
}
|
|
221
|
-
// --- Lightweight frontmatter parser (avoids circular imports) ---
|
|
222
|
-
const FM_RE = /^---\n([\s\S]*?)\n---\n/;
|
|
223
|
-
const FM_KV_RE = /^(\w+):\s*(.+)$/;
|
|
224
|
-
function parseFrontmatterLocal(content) {
|
|
225
|
-
const match = FM_RE.exec(content);
|
|
226
|
-
if (!match?.[1]) {
|
|
227
|
-
return { metadata: {} };
|
|
228
|
-
}
|
|
229
|
-
const metadata = {};
|
|
230
|
-
for (const line of match[1].split('\n')) {
|
|
231
|
-
const kv = FM_KV_RE.exec(line.trim());
|
|
232
|
-
if (!kv?.[1] || kv[2] === undefined) {
|
|
233
|
-
continue;
|
|
234
|
-
}
|
|
235
|
-
const key = kv[1];
|
|
236
|
-
const val = kv[2].trim();
|
|
237
|
-
if (val.startsWith('[') && val.endsWith(']')) {
|
|
238
|
-
metadata[key] = val
|
|
239
|
-
.slice(1, -1)
|
|
240
|
-
.split(',')
|
|
241
|
-
.map((s) => s.trim().replace(/^["']|["']$/g, ''));
|
|
242
|
-
}
|
|
243
|
-
else if (val === 'null') {
|
|
244
|
-
metadata[key] = null;
|
|
245
|
-
}
|
|
246
|
-
else if (/^\d+$/.test(val)) {
|
|
247
|
-
metadata[key] = parseInt(val, 10);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
metadata[key] = val.replace(/^["']|["']$/g, '');
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
return { metadata };
|
|
254
|
-
}
|
|
255
|
-
// --- Validation helpers for frontmatter values ---
|
|
256
|
-
const SPEC_TYPES = new Set([
|
|
257
|
-
'feature',
|
|
258
|
-
'refactor',
|
|
259
|
-
'bugfix',
|
|
260
|
-
'infra',
|
|
261
|
-
'docs',
|
|
262
|
-
'project',
|
|
263
|
-
'agent',
|
|
264
|
-
'tech-debt',
|
|
265
|
-
]);
|
|
266
|
-
const SPEC_SCOPES = new Set(['trivial', 'feature', 'cross-module', 'architectural']);
|
|
267
|
-
const SPEC_STATUSES = new Set([
|
|
268
|
-
'draft',
|
|
269
|
-
'review',
|
|
270
|
-
'approved',
|
|
271
|
-
'implementing',
|
|
272
|
-
'done',
|
|
273
|
-
'discarded',
|
|
274
|
-
]);
|
|
275
|
-
const RISK_LEVELS = new Set(['low', 'medium', 'high', 'critical']);
|
|
276
|
-
const SPEC_TARGETS = new Set([
|
|
277
|
-
'backend',
|
|
278
|
-
'frontend',
|
|
279
|
-
'fullstack',
|
|
280
|
-
'infrastructure',
|
|
281
|
-
'mobile',
|
|
282
|
-
'desktop',
|
|
283
|
-
'cli',
|
|
284
|
-
'api',
|
|
285
|
-
'database',
|
|
286
|
-
'devops',
|
|
287
|
-
'testing',
|
|
288
|
-
'docs',
|
|
289
|
-
]);
|
|
290
|
-
const VALID_DIFFICULTIES = new Set([1, 2, 3, 4, 5]);
|
|
291
|
-
function validSpecType(v) {
|
|
292
|
-
return typeof v === 'string' && SPEC_TYPES.has(v) ? v : null;
|
|
293
|
-
}
|
|
294
|
-
function validSpecScope(v) {
|
|
295
|
-
return typeof v === 'string' && SPEC_SCOPES.has(v) ? v : null;
|
|
296
|
-
}
|
|
297
|
-
function validSpecStatus(v) {
|
|
298
|
-
return typeof v === 'string' && SPEC_STATUSES.has(v) ? v : null;
|
|
299
|
-
}
|
|
300
|
-
function validRiskLevel(v) {
|
|
301
|
-
return typeof v === 'string' && RISK_LEVELS.has(v) ? v : null;
|
|
302
|
-
}
|
|
303
|
-
function validSpecTarget(v) {
|
|
304
|
-
return typeof v === 'string' && SPEC_TARGETS.has(v) ? v : null;
|
|
305
|
-
}
|
|
306
|
-
function validDifficulty(v) {
|
|
307
|
-
const n = typeof v === 'number' ? v : typeof v === 'string' ? parseInt(v, 10) : NaN;
|
|
308
|
-
return !isNaN(n) && VALID_DIFFICULTIES.has(n) ? n : null;
|
|
309
|
-
}
|
|
310
|
-
// --- Spec folder prefix migration (SPEC-XXX-slug) ---
|
|
311
|
-
const SPEC_ID_PREFIX_RE = /^SPEC-\d{3,}/;
|
|
312
|
-
/**
|
|
313
|
-
* Detect specs whose folder name lacks the SPEC-XXX- prefix.
|
|
314
|
-
* E.g., folder is `planu/specs/login-form/` but should be `planu/specs/SPEC-012-login-form/`.
|
|
315
|
-
*/
|
|
316
|
-
export function filterUnprefixedSpecs(specs) {
|
|
317
|
-
return specs.filter((s) => {
|
|
318
|
-
if (!s.specPath) {
|
|
319
|
-
return false;
|
|
320
|
-
}
|
|
321
|
-
const parts = s.specPath.split('/');
|
|
322
|
-
// The folder is the second-to-last segment (before spec.md)
|
|
323
|
-
const folder = parts[parts.length - 2];
|
|
324
|
-
return folder !== undefined && !SPEC_ID_PREFIX_RE.test(folder);
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Rename spec folders on disk and update storage paths to include the SPEC-XXX- prefix.
|
|
329
|
-
*/
|
|
330
|
-
export async function migrateSpecFolderNames(projectPath, specs, deps) {
|
|
331
|
-
const errors = [];
|
|
332
|
-
let migratedCount = 0;
|
|
333
|
-
for (const spec of specs) {
|
|
334
|
-
try {
|
|
335
|
-
const oldDir = dirname(join(projectPath, spec.specPath));
|
|
336
|
-
const parentDir = dirname(oldDir);
|
|
337
|
-
const oldFolderName = oldDir.split('/').pop() ?? '';
|
|
338
|
-
const newFolderName = `${spec.id}-${oldFolderName}`;
|
|
339
|
-
const newDir = join(parentDir, newFolderName);
|
|
340
|
-
// Skip if already correct or target exists
|
|
341
|
-
if (oldFolderName.startsWith(spec.id)) {
|
|
342
|
-
continue;
|
|
343
|
-
}
|
|
344
|
-
try {
|
|
345
|
-
await access(newDir);
|
|
346
|
-
continue; // Target already exists — skip
|
|
347
|
-
}
|
|
348
|
-
catch {
|
|
349
|
-
/* target doesn't exist — proceed */
|
|
350
|
-
}
|
|
351
|
-
await rename(oldDir, newDir);
|
|
352
|
-
// Update all path fields in storage
|
|
353
|
-
const rewrite = (p) => p.replace(`/${oldFolderName}/`, `/${newFolderName}/`);
|
|
354
|
-
const updates = { specPath: rewrite(spec.specPath) };
|
|
355
|
-
if (spec.technicalPath) {
|
|
356
|
-
updates.technicalPath = rewrite(spec.technicalPath);
|
|
357
|
-
}
|
|
358
|
-
if (spec.progressPath) {
|
|
359
|
-
updates.progressPath = rewrite(spec.progressPath);
|
|
360
|
-
}
|
|
361
|
-
if (spec.executiveReportPath) {
|
|
362
|
-
updates.executiveReportPath = rewrite(spec.executiveReportPath);
|
|
363
|
-
}
|
|
364
|
-
if (spec.technicalReportPath) {
|
|
365
|
-
updates.technicalReportPath = rewrite(spec.technicalReportPath);
|
|
366
|
-
}
|
|
367
|
-
if (spec.planPath) {
|
|
368
|
-
updates.planPath = rewrite(spec.planPath);
|
|
369
|
-
}
|
|
370
|
-
await deps.updateSpec(spec.projectId, spec.id, updates);
|
|
371
|
-
migratedCount++;
|
|
372
|
-
}
|
|
373
|
-
catch (err) {
|
|
374
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
375
|
-
errors.push(`${spec.id}: ${msg}`);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
return { migratedCount, errors };
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* Discover specs buried in subcategory folders and flatten them to planu/specs/{slug}/.
|
|
382
|
-
* Also converts old blockquote metadata to YAML frontmatter.
|
|
383
|
-
* This runs on init_project to fix specs created by older Planu versions.
|
|
384
|
-
*/
|
|
385
|
-
export async function discoverAndFlattenSpecs(projectPath) {
|
|
386
|
-
const specsRoot = join(projectPath, 'planu/specs');
|
|
387
|
-
const errors = [];
|
|
388
|
-
let flattenedCount = 0;
|
|
389
|
-
let convertedCount = 0;
|
|
390
|
-
// Recursive scan: find all spec.md files at any depth
|
|
391
|
-
const specFiles = await findSpecFiles(specsRoot, specsRoot);
|
|
392
|
-
for (const relPath of specFiles) {
|
|
393
|
-
// relPath is like "category/slug/spec.md" or "SPEC-001-slug/spec.md"
|
|
394
|
-
const parts = relPath.split('/');
|
|
395
|
-
if (parts.length < 2) {
|
|
396
|
-
continue;
|
|
397
|
-
}
|
|
398
|
-
// Already flat: spec.md is at depth 1 (e.g., "slug/spec.md")
|
|
399
|
-
if (parts.length === 2) {
|
|
400
|
-
// Check if frontmatter conversion needed
|
|
401
|
-
const fullPath = join(specsRoot, relPath);
|
|
402
|
-
try {
|
|
403
|
-
const converted = await convertBlockquoteToFrontmatter(fullPath);
|
|
404
|
-
if (converted) {
|
|
405
|
-
convertedCount++;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
catch (err) {
|
|
409
|
-
errors.push(`frontmatter ${relPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
410
|
-
}
|
|
411
|
-
continue;
|
|
412
|
-
}
|
|
413
|
-
// Nested: "category/slug/spec.md" → move "slug/" to specsRoot
|
|
414
|
-
const category = parts[0] ?? '';
|
|
415
|
-
const slug = parts[1] ?? '';
|
|
416
|
-
const srcDir = join(specsRoot, category, slug);
|
|
417
|
-
const destDir = join(specsRoot, slug);
|
|
418
|
-
try {
|
|
419
|
-
// Check destination doesn't already exist
|
|
420
|
-
try {
|
|
421
|
-
await access(destDir);
|
|
422
|
-
errors.push(`skip ${category}/${slug}: destination already exists`);
|
|
423
|
-
continue;
|
|
424
|
-
}
|
|
425
|
-
catch {
|
|
426
|
-
/* good — destination doesn't exist */
|
|
427
|
-
}
|
|
428
|
-
await rename(srcDir, destDir);
|
|
429
|
-
flattenedCount++;
|
|
430
|
-
// Cleanup empty category folder
|
|
431
|
-
await cleanupEmptyDirs(join(specsRoot, category));
|
|
432
|
-
// Convert frontmatter if needed
|
|
433
|
-
const specMdPath = join(destDir, 'spec.md');
|
|
434
|
-
try {
|
|
435
|
-
const converted = await convertBlockquoteToFrontmatter(specMdPath);
|
|
436
|
-
if (converted) {
|
|
437
|
-
convertedCount++;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
catch {
|
|
441
|
-
/* best-effort */
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
catch (err) {
|
|
445
|
-
errors.push(`flatten ${category}/${slug}: ${err instanceof Error ? err.message : String(err)}`);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
// Phase 2: Rename folders that lack SPEC-XXX- prefix
|
|
449
|
-
// Re-scan after flattening to catch newly moved folders
|
|
450
|
-
let renamedCount = 0;
|
|
451
|
-
try {
|
|
452
|
-
const rootEntries = await readdir(specsRoot, { withFileTypes: true });
|
|
453
|
-
for (const entry of rootEntries) {
|
|
454
|
-
if (!entry.isDirectory() || SPEC_ID_PREFIX_RE.test(entry.name)) {
|
|
455
|
-
continue;
|
|
456
|
-
}
|
|
457
|
-
// Read spec.md to extract SPEC ID from heading
|
|
458
|
-
const specMdPath = join(specsRoot, entry.name, 'spec.md');
|
|
459
|
-
try {
|
|
460
|
-
const content = await readFile(specMdPath, 'utf-8');
|
|
461
|
-
// Try frontmatter title first, then heading
|
|
462
|
-
const headingMatch = /^#\s+(SPEC-\d{3,})/.exec(content.replace(/^---[\s\S]*?---\n*/, ''));
|
|
463
|
-
if (!headingMatch?.[1]) {
|
|
464
|
-
continue;
|
|
465
|
-
}
|
|
466
|
-
const specId = headingMatch[1];
|
|
467
|
-
const newName = `${specId}-${entry.name}`;
|
|
468
|
-
const newDir = join(specsRoot, newName);
|
|
469
|
-
try {
|
|
470
|
-
await access(newDir);
|
|
471
|
-
continue; // Target exists — skip
|
|
472
|
-
}
|
|
473
|
-
catch {
|
|
474
|
-
/* proceed */
|
|
475
|
-
}
|
|
476
|
-
await rename(join(specsRoot, entry.name), newDir);
|
|
477
|
-
renamedCount++;
|
|
478
|
-
}
|
|
479
|
-
catch {
|
|
480
|
-
/* spec.md unreadable — skip */
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
catch {
|
|
485
|
-
/* specsRoot unreadable — skip */
|
|
486
|
-
}
|
|
487
|
-
return { flattenedCount, convertedCount, renamedCount, errors };
|
|
488
|
-
}
|
|
489
|
-
/** Recursively find all spec.md files, returning paths relative to root. */
|
|
490
|
-
async function findSpecFiles(dir, root) {
|
|
491
|
-
const results = [];
|
|
492
|
-
try {
|
|
493
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
494
|
-
for (const entry of entries) {
|
|
495
|
-
const fullPath = join(dir, entry.name);
|
|
496
|
-
if (entry.isDirectory()) {
|
|
497
|
-
const nested = await findSpecFiles(fullPath, root);
|
|
498
|
-
results.push(...nested);
|
|
499
|
-
}
|
|
500
|
-
else if (entry.name === 'spec.md') {
|
|
501
|
-
const rel = fullPath.slice(root.length + 1); // +1 for trailing /
|
|
502
|
-
results.push(rel);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
catch {
|
|
507
|
-
/* directory unreadable */
|
|
508
|
-
}
|
|
509
|
-
return results;
|
|
510
|
-
}
|
|
511
|
-
/** Convert old blockquote metadata to YAML frontmatter. Returns true if converted. */
|
|
512
|
-
async function convertBlockquoteToFrontmatter(specMdPath) {
|
|
513
|
-
const content = await readFile(specMdPath, 'utf-8');
|
|
514
|
-
// Already has frontmatter — skip
|
|
515
|
-
if (content.startsWith('---\n')) {
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
|
-
// Detect blockquote metadata pattern:
|
|
519
|
-
// > **Type:** feature | **Scope:** cross-module | **Target:** backend
|
|
520
|
-
// > **Status:** draft | **Difficulty:** 3/5 | **Risk:** medium
|
|
521
|
-
const blockquoteRe = /^#\s+(?:SPEC-\d+:\s*)?(.+)\n\n(?:>\s+\*\*\w+:\*\*[^\n]+\n)+/;
|
|
522
|
-
const match = blockquoteRe.exec(content);
|
|
523
|
-
if (!match) {
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
const title = match[1]?.trim() ?? '';
|
|
527
|
-
const blockquoteBlock = match[0];
|
|
528
|
-
// Extract fields from blockquote lines
|
|
529
|
-
const fields = {};
|
|
530
|
-
const fieldRe = /\*\*(\w+):\*\*\s*([^|*]+)/g;
|
|
531
|
-
let fm;
|
|
532
|
-
while ((fm = fieldRe.exec(blockquoteBlock)) !== null) {
|
|
533
|
-
const key = (fm[1] ?? '').toLowerCase().trim();
|
|
534
|
-
const val = (fm[2] ?? '').trim();
|
|
535
|
-
if (key && val) {
|
|
536
|
-
fields[key] = val;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
// Build YAML frontmatter
|
|
540
|
-
const yamlLines = ['---'];
|
|
541
|
-
if (title) {
|
|
542
|
-
yamlLines.push(`title: "${title.replace(/"/g, '\\"')}"`);
|
|
543
|
-
}
|
|
544
|
-
if (fields.type) {
|
|
545
|
-
yamlLines.push(`type: ${fields.type}`);
|
|
546
|
-
}
|
|
547
|
-
if (fields.scope) {
|
|
548
|
-
yamlLines.push(`scope: ${fields.scope}`);
|
|
549
|
-
}
|
|
550
|
-
if (fields.target) {
|
|
551
|
-
yamlLines.push(`target: ${fields.target}`);
|
|
552
|
-
}
|
|
553
|
-
if (fields.status) {
|
|
554
|
-
yamlLines.push(`status: ${fields.status}`);
|
|
555
|
-
}
|
|
556
|
-
if (fields.difficulty) {
|
|
557
|
-
yamlLines.push(`difficulty: ${fields.difficulty}`);
|
|
558
|
-
}
|
|
559
|
-
if (fields.risk) {
|
|
560
|
-
yamlLines.push(`risk: ${fields.risk}`);
|
|
561
|
-
}
|
|
562
|
-
yamlLines.push('---');
|
|
563
|
-
yamlLines.push('');
|
|
564
|
-
// Replace blockquote header with frontmatter + clean title
|
|
565
|
-
const rest = content.slice(blockquoteBlock.length);
|
|
566
|
-
const specIdMatch = /^#\s+(SPEC-\d+)/.exec(content);
|
|
567
|
-
const heading = specIdMatch ? `# ${specIdMatch[1]}: ${title}` : `# ${title}`;
|
|
568
|
-
const newContent = yamlLines.join('\n') + heading + '\n' + rest;
|
|
569
|
-
await writeFile(specMdPath, newContent, 'utf-8');
|
|
570
|
-
return true;
|
|
571
|
-
}
|
|
572
|
-
/**
|
|
573
|
-
* Reconcile specs.json paths with actual filesystem locations.
|
|
574
|
-
* After discoverAndFlattenSpecs() renames/moves folders, specs.json may still
|
|
575
|
-
* reference old paths. This function detects broken paths and fixes them by
|
|
576
|
-
* scanning planu/specs/ for the matching SPEC-XXX folder.
|
|
577
|
-
*/
|
|
578
|
-
export async function reconcileSpecPaths(projectPath, deps, projectId) {
|
|
579
|
-
const specs = await deps.listSpecs(projectId);
|
|
580
|
-
const specsRoot = join(projectPath, 'planu/specs');
|
|
581
|
-
const errors = [];
|
|
582
|
-
let reconciledCount = 0;
|
|
583
|
-
// Build a map of actual folder names on disk for fast lookup
|
|
584
|
-
let entries;
|
|
585
|
-
try {
|
|
586
|
-
entries = await readdir(specsRoot, { withFileTypes: true });
|
|
587
|
-
}
|
|
588
|
-
catch {
|
|
589
|
-
return { reconciledCount: 0, errors: [] };
|
|
590
|
-
}
|
|
591
|
-
const diskFolders = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
592
|
-
for (const spec of specs) {
|
|
593
|
-
if (!spec.specPath) {
|
|
594
|
-
continue;
|
|
595
|
-
}
|
|
596
|
-
// Check if current specPath exists on disk
|
|
597
|
-
const fullSpecPath = join(projectPath, spec.specPath);
|
|
598
|
-
try {
|
|
599
|
-
await access(fullSpecPath);
|
|
600
|
-
continue; // Path is valid — skip
|
|
601
|
-
}
|
|
602
|
-
catch {
|
|
603
|
-
/* path broken — needs reconciliation */
|
|
604
|
-
}
|
|
605
|
-
// Find the matching folder by SPEC ID prefix (e.g., SPEC-001-)
|
|
606
|
-
const matchingFolder = diskFolders.find((f) => f.startsWith(`${spec.id}-`));
|
|
607
|
-
if (!matchingFolder) {
|
|
608
|
-
continue; // No matching folder found — can't fix
|
|
609
|
-
}
|
|
610
|
-
// Build updated paths
|
|
611
|
-
const newFolder = `planu/specs/${matchingFolder}`;
|
|
612
|
-
const rewrite = (p) => {
|
|
613
|
-
// Replace the entire folder path portion, preserving the filename
|
|
614
|
-
const filename = p.split('/').pop() ?? '';
|
|
615
|
-
return `${newFolder}/${filename}`;
|
|
616
|
-
};
|
|
617
|
-
try {
|
|
618
|
-
const updates = {
|
|
619
|
-
specPath: rewrite(spec.specPath),
|
|
620
|
-
};
|
|
621
|
-
if (spec.technicalPath) {
|
|
622
|
-
updates.technicalPath = rewrite(spec.technicalPath);
|
|
623
|
-
}
|
|
624
|
-
if (spec.progressPath) {
|
|
625
|
-
updates.progressPath = rewrite(spec.progressPath);
|
|
626
|
-
}
|
|
627
|
-
if (spec.executiveReportPath) {
|
|
628
|
-
updates.executiveReportPath = rewrite(spec.executiveReportPath);
|
|
629
|
-
}
|
|
630
|
-
if (spec.technicalReportPath) {
|
|
631
|
-
updates.technicalReportPath = rewrite(spec.technicalReportPath);
|
|
632
|
-
}
|
|
633
|
-
if (spec.planPath) {
|
|
634
|
-
updates.planPath = rewrite(spec.planPath);
|
|
635
|
-
}
|
|
636
|
-
await deps.updateSpec(projectId, spec.id, updates);
|
|
637
|
-
reconciledCount++;
|
|
638
|
-
}
|
|
639
|
-
catch (err) {
|
|
640
|
-
errors.push(`${spec.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return { reconciledCount, errors };
|
|
644
|
-
}
|
|
645
|
-
/**
|
|
646
|
-
* Check if a project path has a planu.json with legacy specLocation.
|
|
647
|
-
*/
|
|
648
|
-
export async function hasLegacyConfig(projectPath) {
|
|
649
|
-
try {
|
|
650
|
-
await access(join(projectPath, 'planu.json'));
|
|
651
|
-
const content = await readFile(join(projectPath, 'planu.json'), 'utf-8');
|
|
652
|
-
const config = JSON.parse(content);
|
|
653
|
-
return config.specLocation ? isLegacyLocation(config.specLocation) : false;
|
|
654
|
-
}
|
|
655
|
-
catch {
|
|
656
|
-
return false;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
1
|
+
// spec-migrator.ts — Re-export barrel (implementation split into spec-migrator/)
|
|
2
|
+
export * from './spec-migrator/index.js';
|
|
659
3
|
//# sourceMappingURL=spec-migrator.js.map
|