@namewta/speculo 0.1.21 → 0.2.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/README.md +68 -78
- package/dist/src/cli.js +57 -34
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +1 -3
- package/dist/src/index.js +128 -168
- package/dist/src/index.js.map +1 -1
- package/dist/src/migrate.d.ts +38 -0
- package/dist/src/migrate.js +646 -0
- package/dist/src/migrate.js.map +1 -0
- package/dist/src/workflows.d.ts +7 -37
- package/dist/src/workflows.js +49 -123
- package/dist/src/workflows.js.map +1 -1
- package/package.json +6 -4
- package/template/.speculo/README.md +20 -0
- package/template/.speculo/workspace.json +12 -0
- package/template/commands/docs-sync.md +28 -0
- package/template/commands/finalize.md +37 -0
- package/template/commands/knowledge-prune.md +20 -0
- package/template/commands/retro.md +15 -8
- package/template/commands/status.md +8 -51
- package/template/skills/agents-md-builder/SKILL.md +14 -101
- package/template/skills/change-lifecycle/SKILL.md +25 -0
- package/template/{workflows/dev/_templates → skills/change-lifecycle/assets}/completion-summary-template.md +2 -2
- package/template/{workflows/dev/_templates → skills/change-lifecycle/assets}/completion-verification-template.md +1 -1
- package/template/skills/change-lifecycle/references/completion-gate.md +19 -0
- package/template/skills/change-lifecycle/references/finalize-archive.md +32 -0
- package/template/skills/docs-sync/SKILL.md +22 -0
- package/template/skills/docs-sync/assets/report-template.md +45 -0
- package/template/skills/docs-sync/assets/state-template.json +20 -0
- package/template/skills/docs-sync/assets/workflow-scope-template.json +9 -0
- package/template/skills/docs-sync/references/agents-contract.md +43 -0
- package/template/skills/docs-sync/references/changelog-contract.md +39 -0
- package/template/skills/docs-sync/references/document-lifecycle-contract.md +38 -0
- package/template/skills/docs-sync/references/git-state-contract.md +67 -0
- package/template/skills/docs-sync/references/readme-contract.md +44 -0
- package/template/skills/docs-sync/references/workflow-scope-contract.md +50 -0
- package/template/skills/github-npm-ops/SKILL.md +14 -39
- package/template/skills/github-npm-ops/references/failure-recovery.md +1 -1
- package/template/skills/github-npm-ops/references/preflight-checklist.md +1 -1
- package/template/skills/github-npm-ops/references/release-notes-injection.md +1 -1
- package/template/skills/github-npm-ops/references/release-pipeline.md +13 -13
- package/template/skills/github-npm-ops/references/version-bump-flow.md +3 -3
- package/template/skills/knowledge-prune/SKILL.md +29 -0
- package/template/skills/knowledge-prune/references/audit-rules.md +24 -0
- package/template/skills/runtime-context/SKILL.md +43 -0
- package/template/skills/runtime-context/references/path-resolution.md +32 -0
- package/template/skills/speculo-retro/SKILL.md +13 -37
- package/template/skills/speculo-retro/references/friction-taxonomy.md +3 -3
- package/template/skills/speculo-retro/references/issue-drafting-sop.md +3 -3
- package/template/skills/worktree-isolation/SKILL.md +10 -46
- package/template/skills/worktree-isolation/references/audit-branch-tree.md +2 -2
- package/template/skills/worktree-isolation/references/create-worktree.md +6 -6
- package/template/skills/worktree-isolation/references/merge-and-cleanup.md +5 -5
- package/template/vendor/README.md +11 -10
- package/template/vendor/matt-pocock/README.md +41 -0
- package/template/vendor/matt-pocock/engineering/README.md +28 -0
- package/template/vendor/matt-pocock/engineering/ask-matt/SKILL.md +76 -0
- package/template/vendor/matt-pocock/engineering/code-review/SKILL.md +89 -0
- package/template/vendor/matt-pocock/engineering/codebase-design/DEEPENING.md +37 -0
- package/template/vendor/matt-pocock/engineering/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/template/vendor/matt-pocock/engineering/codebase-design/SKILL.md +114 -0
- package/template/vendor/matt-pocock/engineering/diagnosing-bugs/SKILL.md +134 -0
- package/template/vendor/matt-pocock/engineering/diagnosing-bugs/scripts/hitl-loop.template.sh +41 -0
- package/template/vendor/matt-pocock/engineering/domain-modeling/ADR-FORMAT.md +47 -0
- package/template/vendor/matt-pocock/engineering/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/template/vendor/matt-pocock/engineering/domain-modeling/SKILL.md +74 -0
- package/template/vendor/matt-pocock/engineering/grill-with-docs/SKILL.md +7 -0
- package/template/vendor/matt-pocock/engineering/implement/SKILL.md +15 -0
- package/template/vendor/matt-pocock/engineering/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/template/vendor/matt-pocock/engineering/improve-codebase-architecture/SKILL.md +66 -0
- package/template/vendor/matt-pocock/engineering/prototype/LOGIC.md +79 -0
- package/template/vendor/matt-pocock/engineering/prototype/SKILL.md +30 -0
- package/template/vendor/matt-pocock/engineering/prototype/UI.md +112 -0
- package/template/vendor/matt-pocock/engineering/research/SKILL.md +12 -0
- package/template/vendor/matt-pocock/engineering/resolving-merge-conflicts/SKILL.md +14 -0
- package/template/vendor/matt-pocock/engineering/setup-matt-pocock-skills/SKILL.md +127 -0
- package/template/vendor/matt-pocock/engineering/setup-matt-pocock-skills/domain.md +51 -0
- package/template/vendor/matt-pocock/engineering/setup-matt-pocock-skills/issue-tracker-github.md +45 -0
- package/template/vendor/matt-pocock/engineering/setup-matt-pocock-skills/issue-tracker-gitlab.md +46 -0
- package/template/vendor/matt-pocock/engineering/setup-matt-pocock-skills/issue-tracker-local.md +30 -0
- package/template/vendor/matt-pocock/engineering/setup-matt-pocock-skills/triage-labels.md +15 -0
- package/template/vendor/matt-pocock/engineering/tdd/SKILL.md +36 -0
- package/template/vendor/matt-pocock/engineering/tdd/mocking.md +59 -0
- package/template/vendor/matt-pocock/engineering/tdd/tests.md +77 -0
- package/template/vendor/matt-pocock/engineering/to-spec/SKILL.md +75 -0
- package/template/vendor/matt-pocock/engineering/to-tickets/SKILL.md +113 -0
- package/template/vendor/matt-pocock/engineering/triage/AGENT-BRIEF.md +204 -0
- package/template/vendor/matt-pocock/engineering/triage/OUT-OF-SCOPE.md +104 -0
- package/template/vendor/matt-pocock/engineering/triage/SKILL.md +112 -0
- package/template/vendor/matt-pocock/engineering/wayfinder/SKILL.md +127 -0
- package/template/vendor/matt-pocock/in-progress/README.md +10 -0
- package/template/vendor/matt-pocock/in-progress/claude-handoff/SKILL.md +18 -0
- package/template/vendor/matt-pocock/in-progress/loop-me/SKILL.md +32 -0
- package/template/vendor/matt-pocock/in-progress/wizard/SKILL.md +45 -0
- package/template/vendor/matt-pocock/in-progress/wizard/template.sh +211 -0
- package/template/vendor/matt-pocock/in-progress/writing-beats/SKILL.md +67 -0
- package/template/vendor/matt-pocock/in-progress/writing-fragments/SKILL.md +78 -0
- package/template/vendor/matt-pocock/in-progress/writing-shape/SKILL.md +79 -0
- package/template/vendor/matt-pocock/productivity/README.md +18 -0
- package/template/vendor/matt-pocock/productivity/grill-me/SKILL.md +7 -0
- package/template/vendor/matt-pocock/productivity/grilling/SKILL.md +12 -0
- package/template/vendor/matt-pocock/productivity/handoff/SKILL.md +16 -0
- package/template/vendor/matt-pocock/productivity/teach/GLOSSARY-FORMAT.md +35 -0
- package/template/vendor/matt-pocock/productivity/teach/LEARNING-RECORD-FORMAT.md +46 -0
- package/template/vendor/matt-pocock/productivity/teach/MISSION-FORMAT.md +31 -0
- package/template/vendor/matt-pocock/productivity/teach/RESOURCES-FORMAT.md +32 -0
- package/template/vendor/matt-pocock/productivity/teach/SKILL.md +140 -0
- package/template/vendor/matt-pocock/productivity/writing-great-skills/GLOSSARY.md +201 -0
- package/template/vendor/matt-pocock/productivity/writing-great-skills/SKILL.md +83 -0
- package/template/workflows/matt-pocock/WORKFLOW.md +145 -0
- package/template/workflows/matt-pocock/_state/status.json +5 -0
- package/template/workflows/matt-pocock/routes/architecture.md +24 -0
- package/template/workflows/matt-pocock/routes/diagnose.md +22 -0
- package/template/workflows/matt-pocock/routes/experimental.md +18 -0
- package/template/workflows/matt-pocock/routes/idea-to-delivery.md +63 -0
- package/template/workflows/matt-pocock/routes/merge-conflicts.md +19 -0
- package/template/workflows/matt-pocock/routes/productivity.md +25 -0
- package/template/workflows/matt-pocock/routes/research-prototype.md +20 -0
- package/template/workflows/matt-pocock/routes/review.md +19 -0
- package/template/workflows/matt-pocock/routes/setup.md +42 -0
- package/template/workflows/matt-pocock/routes/triage.md +25 -0
- package/template/workflows/matt-pocock/routes/wayfinder.md +27 -0
- package/template/workflows/person/M-mao-zedong-cognitive-os/M-mao-zedong-cognitive-os.md +74 -59
- package/template/workflows/person/M-mao-zedong-cognitive-os/activate.md +2 -2
- package/template/workflows/person/M-mao-zedong-cognitive-os/deliver.md +5 -5
- package/template/workflows/person/M-mao-zedong-cognitive-os/diagnose.md +2 -2
- package/template/workflows/person/M-mao-zedong-cognitive-os/mobilize.md +4 -4
- package/template/workflows/person/M-mao-zedong-cognitive-os/strategize.md +3 -3
- package/template/workflows/person/WORKFLOW.md +68 -0
- package/template/workflows/person/_state/.config/LESSONS.md +3 -0
- package/template/workflows/person/_state/.config/RULES.md +3 -0
- package/template/workflows/person/_state/changes/.gitkeep +1 -0
- package/template/workflows/person/_state/status.json +5 -0
- package/template/.speculo/.config/LESSONS.md +0 -9
- package/template/.speculo/.config/RULES.md +0 -11
- package/template/.speculo/AGENTS.md +0 -30
- package/template/.speculo/archive/AGENTS.md +0 -28
- package/template/.speculo/archive/dev/.gitkeep +0 -0
- package/template/.speculo/archive/person/.gitkeep +0 -0
- package/template/.speculo/dev/.gitkeep +0 -0
- package/template/.speculo/dev/docs-sync-state.json +0 -14
- package/template/.speculo/dev-status.json +0 -3
- package/template/.speculo/doc-status.json +0 -3
- package/template/.speculo/person/.gitkeep +0 -0
- package/template/.speculo/person-status.json +0 -1
- package/template/commands/archive.md +0 -68
- package/template/commands/caveman.md +0 -50
- package/template/commands/config-prune.md +0 -59
- package/template/commands/grill-me.md +0 -48
- package/template/commands/handoff.md +0 -59
- package/template/commands/scaffold-exercises.md +0 -56
- package/template/commands/write-a-skill.md +0 -52
- package/template/skills/caveman/SKILL.md +0 -38
- package/template/skills/caveman/references/compression-rules.md +0 -102
- package/template/skills/config-prune/SKILL.md +0 -44
- package/template/skills/config-prune/references/audit-rules.md +0 -38
- package/template/skills/grill-me/SKILL.md +0 -40
- package/template/skills/handoff/SKILL.md +0 -73
- package/template/skills/scaffold-exercises/SKILL.md +0 -41
- package/template/skills/scaffold-exercises/references/exercise-structure.md +0 -85
- package/template/skills/scaffold-exercises/references/lint-and-git.md +0 -54
- package/template/skills/speculo-write/SKILL.md +0 -56
- package/template/skills/speculo-write/references/asset-selection-sop.md +0 -67
- package/template/skills/speculo-write/references/authoring-quality-levers.md +0 -61
- package/template/skills/speculo-write/references/command-authoring-sop.md +0 -98
- package/template/skills/speculo-write/references/migration-sop.md +0 -101
- package/template/skills/speculo-write/references/persistence-contract-sop.md +0 -271
- package/template/skills/speculo-write/references/skill-authoring-sop.md +0 -212
- package/template/skills/speculo-write/references/validation-checklist.md +0 -85
- package/template/skills/speculo-write/references/workflow-authoring-sop.md +0 -165
- package/template/vendor/codebase-design/DEEPENING.md +0 -37
- package/template/vendor/codebase-design/DESIGN-IT-TWICE.md +0 -44
- package/template/vendor/codebase-design/SKILL.md +0 -114
- package/template/vendor/officecli/SKILL.md +0 -415
- package/template/vendor/resolving-merge-conflicts/SKILL.md +0 -14
- package/template/workflows/dev/01-grill-with-docs/01-grill-with-docs.md +0 -107
- package/template/workflows/dev/01-grill-with-docs/grill-context-scan.md +0 -30
- package/template/workflows/dev/01-grill-with-docs/grill-decision.md +0 -38
- package/template/workflows/dev/02-prd/02-prd.md +0 -70
- package/template/workflows/dev/02-prd/prd-synthesis.md +0 -30
- package/template/workflows/dev/02-prd/prd-zoom-out.md +0 -29
- package/template/workflows/dev/03-tdd/03-tdd.md +0 -55
- package/template/workflows/dev/03-tdd/agents/tdd-finish-agent.md +0 -34
- package/template/workflows/dev/03-tdd/agents/tdd-implement-agent.md +0 -34
- package/template/workflows/dev/03-tdd/agents/tdd-plan-agent.md +0 -34
- package/template/workflows/dev/03-tdd/mocking.md +0 -43
- package/template/workflows/dev/03-tdd/refactoring.md +0 -10
- package/template/workflows/dev/03-tdd/tdd-finish.md +0 -34
- package/template/workflows/dev/03-tdd/tdd-loop.md +0 -36
- package/template/workflows/dev/03-tdd/tdd-plan.md +0 -37
- package/template/workflows/dev/03-tdd/tests.md +0 -61
- package/template/workflows/dev/04-finalize/04-finalize.md +0 -57
- package/template/workflows/dev/04-finalize/agents/completion-gate-agent.md +0 -35
- package/template/workflows/dev/04-finalize/completion-gate.md +0 -41
- package/template/workflows/dev/04-finalize/finalize-archive.md +0 -55
- package/template/workflows/dev/A-improve-architecture/A-improve-architecture.md +0 -60
- package/template/workflows/dev/A-improve-architecture/HTML-REPORT.md +0 -123
- package/template/workflows/dev/A-improve-architecture/architecture-grill.md +0 -30
- package/template/workflows/dev/A-improve-architecture/architecture-review.md +0 -29
- package/template/workflows/dev/A-improve-architecture/architecture-scan.md +0 -37
- package/template/workflows/dev/AGENTS.md +0 -95
- package/template/workflows/dev/D-docs-sync/D-docs-sync.md +0 -140
- package/template/workflows/dev/D-docs-sync/agents/docs-diff-agent.md +0 -34
- package/template/workflows/dev/D-docs-sync/agents/docs-update-agent.md +0 -34
- package/template/workflows/dev/D-docs-sync/agents-contract.md +0 -95
- package/template/workflows/dev/D-docs-sync/changelog-contract.md +0 -155
- package/template/workflows/dev/D-docs-sync/config-contract.md +0 -75
- package/template/workflows/dev/D-docs-sync/docs-sync-diff.md +0 -86
- package/template/workflows/dev/D-docs-sync/docs-sync-finish.md +0 -37
- package/template/workflows/dev/D-docs-sync/docs-sync-state.md +0 -47
- package/template/workflows/dev/D-docs-sync/docs-sync-update.md +0 -44
- package/template/workflows/dev/D-docs-sync/knowledge-extract.md +0 -66
- package/template/workflows/dev/D-docs-sync/readme-contract.md +0 -124
- package/template/workflows/dev/D-docs-sync/state-json-schema.md +0 -172
- package/template/workflows/dev/H-diagnose/H-diagnose.md +0 -108
- package/template/workflows/dev/H-diagnose/agents/diagnose-agent.md +0 -33
- package/template/workflows/dev/H-diagnose/agents/fix-agent.md +0 -34
- package/template/workflows/dev/H-diagnose/diagnose-fix.md +0 -34
- package/template/workflows/dev/H-diagnose/diagnose-guide.md +0 -144
- package/template/workflows/dev/H-diagnose/diagnose-loop.md +0 -41
- package/template/workflows/dev/H-diagnose/scripts/hitl-loop.template.sh +0 -41
- package/template/workflows/dev/I-to-issues/I-to-issues.md +0 -79
- package/template/workflows/dev/I-to-issues/issues-slices.md +0 -211
- package/template/workflows/dev/M-domain-modeling/ADR-FORMAT.md +0 -74
- package/template/workflows/dev/M-domain-modeling/CONTEXT-FORMAT.md +0 -67
- package/template/workflows/dev/M-domain-modeling/M-domain-modeling.md +0 -102
- package/template/workflows/dev/R-review/R-review.md +0 -75
- package/template/workflows/dev/R-review/agents/engineering-review-agent.md +0 -33
- package/template/workflows/dev/R-review/agents/spec-review-agent.md +0 -34
- package/template/workflows/dev/R-review/agents/standards-review-agent.md +0 -34
- package/template/workflows/dev/R-review/code-quality-checklist.md +0 -118
- package/template/workflows/dev/R-review/removal-checklist.md +0 -53
- package/template/workflows/dev/R-review/review-axes.md +0 -61
- package/template/workflows/dev/R-review/review-setup.md +0 -111
- package/template/workflows/dev/R-review/review-verdict.md +0 -43
- package/template/workflows/dev/R-review/security-checklist.md +0 -126
- package/template/workflows/dev/R-review/solid-checklist.md +0 -73
- package/template/workflows/dev/_templates/diagnosis-template.md +0 -20
- package/template/workflows/dev/_templates/docs-sync-report-template.md +0 -45
- package/template/workflows/dev/_templates/docs-sync-state-template.json +0 -14
- package/template/workflows/dev/_templates/domain-model-log-template.md +0 -20
- package/template/workflows/dev/_templates/grill-context-map-template.md +0 -20
- package/template/workflows/dev/_templates/grill-decision-log-template.md +0 -20
- package/template/workflows/dev/_templates/issues-slices-template.md +0 -106
- package/template/workflows/dev/_templates/overview-template.md +0 -19
- package/template/workflows/dev/_templates/prd-template.md +0 -26
- package/template/workflows/dev/_templates/regression-template.md +0 -20
- package/template/workflows/dev/_templates/review-report-template.md +0 -30
- package/template/workflows/dev/_templates/review-sources-template.md +0 -33
- package/template/workflows/dev/_templates/review-verdict-template.md +0 -33
- package/template/workflows/dev/_templates/tdd-log-template.md +0 -23
- package/template/workflows/dev/_templates/tdd-plan-template.md +0 -35
- package/template/workflows/dev/_templates/tdd-verification-template.md +0 -26
- package/template/workflows/doc/AGENTS.md +0 -80
- package/template/workflows/doc/B-writing-beats/B-writing-beats.md +0 -79
- package/template/workflows/doc/B-writing-beats/writing-beats-append.md +0 -31
- package/template/workflows/doc/B-writing-beats/writing-beats-options.md +0 -29
- package/template/workflows/doc/E-edit-article/E-edit-article.md +0 -79
- package/template/workflows/doc/E-edit-article/edit-article-plan.md +0 -30
- package/template/workflows/doc/E-edit-article/edit-article-rewrite.md +0 -31
- package/template/workflows/doc/F-writing-fragments/F-writing-fragments.md +0 -80
- package/template/workflows/doc/F-writing-fragments/writing-fragments-interview.md +0 -32
- package/template/workflows/doc/F-writing-fragments/writing-fragments-log.md +0 -29
- package/template/workflows/doc/S-writing-shape/S-writing-shape.md +0 -81
- package/template/workflows/doc/S-writing-shape/writing-shape-block.md +0 -32
- package/template/workflows/doc/S-writing-shape/writing-shape-opening.md +0 -27
- package/template/workflows/doc/T-teach/T-teach.md +0 -64
- package/template/workflows/doc/T-teach/teach-lesson-wrap.md +0 -63
- package/template/workflows/doc/T-teach/teach-lesson.md +0 -53
- package/template/workflows/doc/T-teach/teach-mission.md +0 -33
- package/template/workflows/doc/T-teach/teach-resources.md +0 -36
- package/template/workflows/doc/_templates/edit-article-plan-template.md +0 -25
- package/template/workflows/doc/_templates/edit-article-template.md +0 -7
- package/template/workflows/doc/_templates/teach-glossary-template.md +0 -26
- package/template/workflows/doc/_templates/teach-learning-record-template.md +0 -38
- package/template/workflows/doc/_templates/teach-lesson-html-template.md +0 -24
- package/template/workflows/doc/_templates/teach-mission-template.md +0 -19
- package/template/workflows/doc/_templates/teach-resources-template.md +0 -18
- package/template/workflows/doc/_templates/writing-article-template.md +0 -7
- package/template/workflows/doc/_templates/writing-beat-options-template.md +0 -21
- package/template/workflows/doc/_templates/writing-fragments-template.md +0 -7
- package/template/workflows/doc/_templates/writing-interview-log-template.md +0 -21
- package/template/workflows/doc/_templates/writing-shape-log-template.md +0 -25
- package/template/workflows/person/AGENTS.md +0 -72
- /package/template/{.speculo/.config/adr → workflows/matt-pocock/_state/archive}/.gitkeep +0 -0
- /package/template/{.speculo/.config/context → workflows/matt-pocock/_state/changes}/.gitkeep +0 -0
- /package/template/{.speculo/archive/doc → workflows/person/_state/.config/context}/.gitkeep +0 -0
- /package/template/{.speculo/doc → workflows/person/_state/archive}/.gitkeep +0 -0
|
@@ -0,0 +1,646 @@
|
|
|
1
|
+
import { cp, mkdir, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
3
|
+
import { pathExists } from "./utils.js";
|
|
4
|
+
const V2_MARKERS = [
|
|
5
|
+
"dev-status.json",
|
|
6
|
+
"doc-status.json",
|
|
7
|
+
"person-status.json",
|
|
8
|
+
".config",
|
|
9
|
+
"archive",
|
|
10
|
+
"dev",
|
|
11
|
+
"doc",
|
|
12
|
+
];
|
|
13
|
+
const TRANSITIONAL_DOCS_STATE = join("commands", ".config", "docs-sync-state.json");
|
|
14
|
+
const COMMAND_RUN_RE = /^\d{4}-\d{2}-\d{2}-[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
15
|
+
const LEGACY_ROOT_ENTRIES = new Set([
|
|
16
|
+
"AGENTS.md",
|
|
17
|
+
".config",
|
|
18
|
+
"archive",
|
|
19
|
+
"commands",
|
|
20
|
+
"dev",
|
|
21
|
+
"doc",
|
|
22
|
+
"person",
|
|
23
|
+
"dev-status.json",
|
|
24
|
+
"doc-status.json",
|
|
25
|
+
"person-status.json",
|
|
26
|
+
".DS_Store",
|
|
27
|
+
]);
|
|
28
|
+
const CHANGE_NAME_RE = /^(\d{4})-(\d{2})-(\d{2})-([a-z0-9]+(?:-[a-z0-9]+)*)$/;
|
|
29
|
+
function installRoot(target) {
|
|
30
|
+
return join(resolve(target), "speculo");
|
|
31
|
+
}
|
|
32
|
+
function stateRoot(target) {
|
|
33
|
+
return join(installRoot(target), ".speculo");
|
|
34
|
+
}
|
|
35
|
+
async function directoryNames(path) {
|
|
36
|
+
if (!(await pathExists(path)))
|
|
37
|
+
return [];
|
|
38
|
+
const entries = await readdir(path, { withFileTypes: true });
|
|
39
|
+
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
40
|
+
}
|
|
41
|
+
async function readStatus(path, blockers) {
|
|
42
|
+
if (!(await pathExists(path))) {
|
|
43
|
+
blockers.push("Missing change status: " + path);
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
blockers.push("Invalid change status JSON: " + path);
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function validateLegacyIndex(path, blockers) {
|
|
55
|
+
if (!(await pathExists(path)))
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
59
|
+
if (!value ||
|
|
60
|
+
typeof value !== "object" ||
|
|
61
|
+
!Array.isArray(value.active)) {
|
|
62
|
+
blockers.push("Invalid legacy workflow index shape: " + path);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
blockers.push("Invalid legacy workflow index JSON: " + path);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function readLegacyDocsSyncState(path, blockers) {
|
|
70
|
+
if (!(await pathExists(path)))
|
|
71
|
+
return undefined;
|
|
72
|
+
try {
|
|
73
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
74
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
75
|
+
blockers.push("Invalid legacy docs-sync state shape: " + path);
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
const state = value;
|
|
79
|
+
for (const key of ["tracked_docs", "tracked_assets", "synced_docs", "synced_assets"]) {
|
|
80
|
+
const entries = state[key];
|
|
81
|
+
if (entries !== undefined &&
|
|
82
|
+
(!Array.isArray(entries) || entries.some((entry) => typeof entry !== "string"))) {
|
|
83
|
+
blockers.push("Invalid legacy docs-sync " + key + ": " + path);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
for (const key of ["last_sync_sha", "previous_sync_sha"]) {
|
|
87
|
+
const sha = state[key];
|
|
88
|
+
if (sha !== undefined && sha !== null && typeof sha !== "string") {
|
|
89
|
+
blockers.push("Invalid legacy docs-sync " + key + ": " + path);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return state;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
blockers.push("Invalid legacy docs-sync state JSON: " + path);
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function legacyName(source, originalName) {
|
|
100
|
+
const match = originalName.match(CHANGE_NAME_RE);
|
|
101
|
+
if (!match)
|
|
102
|
+
return originalName;
|
|
103
|
+
return match[1] + "-" + match[2] + "-" + match[3] +
|
|
104
|
+
"-legacy-" + source + "-" + match[4];
|
|
105
|
+
}
|
|
106
|
+
function monthFromName(name) {
|
|
107
|
+
return name.slice(0, 7);
|
|
108
|
+
}
|
|
109
|
+
async function collectActiveChanges(root, workflow, blockers) {
|
|
110
|
+
const workflowRoot = join(root, workflow);
|
|
111
|
+
const names = await directoryNames(workflowRoot);
|
|
112
|
+
const changes = [];
|
|
113
|
+
for (const name of names) {
|
|
114
|
+
if (!CHANGE_NAME_RE.test(name)) {
|
|
115
|
+
blockers.push("Malformed legacy change directory: " + join(workflowRoot, name));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const status = await readStatus(join(workflowRoot, name, ".status.json"), blockers);
|
|
119
|
+
if (!status)
|
|
120
|
+
continue;
|
|
121
|
+
if (workflow === "person") {
|
|
122
|
+
changes.push({
|
|
123
|
+
sourceWorkflow: workflow,
|
|
124
|
+
sourcePath: join(workflowRoot, name),
|
|
125
|
+
originalName: name,
|
|
126
|
+
destinationRelative: join("person", "changes", name),
|
|
127
|
+
archived: false,
|
|
128
|
+
status,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const destinationName = legacyName(workflow, name);
|
|
133
|
+
changes.push({
|
|
134
|
+
sourceWorkflow: workflow,
|
|
135
|
+
sourcePath: join(workflowRoot, name),
|
|
136
|
+
originalName: name,
|
|
137
|
+
destinationRelative: join("matt-pocock", "archive", monthFromName(name), destinationName),
|
|
138
|
+
archived: true,
|
|
139
|
+
status,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return changes;
|
|
144
|
+
}
|
|
145
|
+
async function collectArchivedChanges(root, workflow, blockers) {
|
|
146
|
+
const workflowArchive = join(root, "archive", workflow);
|
|
147
|
+
const months = await directoryNames(workflowArchive);
|
|
148
|
+
const changes = [];
|
|
149
|
+
for (const month of months) {
|
|
150
|
+
if (!/^\d{4}-\d{2}$/.test(month)) {
|
|
151
|
+
blockers.push("Malformed legacy archive month: " + join(workflowArchive, month));
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
for (const name of await directoryNames(join(workflowArchive, month))) {
|
|
155
|
+
if (!CHANGE_NAME_RE.test(name)) {
|
|
156
|
+
blockers.push("Malformed legacy archived change: " +
|
|
157
|
+
join(workflowArchive, month, name));
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const status = await readStatus(join(workflowArchive, month, name, ".status.json"), blockers);
|
|
161
|
+
if (!status)
|
|
162
|
+
continue;
|
|
163
|
+
if (workflow === "person") {
|
|
164
|
+
changes.push({
|
|
165
|
+
sourceWorkflow: workflow,
|
|
166
|
+
sourcePath: join(workflowArchive, month, name),
|
|
167
|
+
originalName: name,
|
|
168
|
+
destinationRelative: join("person", "archive", month, name),
|
|
169
|
+
archived: true,
|
|
170
|
+
status,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
const destinationName = legacyName(workflow, name);
|
|
175
|
+
changes.push({
|
|
176
|
+
sourceWorkflow: workflow,
|
|
177
|
+
sourcePath: join(workflowArchive, month, name),
|
|
178
|
+
originalName: name,
|
|
179
|
+
destinationRelative: join("matt-pocock", "archive", month, destinationName),
|
|
180
|
+
archived: true,
|
|
181
|
+
status,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return changes;
|
|
187
|
+
}
|
|
188
|
+
export async function detectLegacyState(target) {
|
|
189
|
+
const root = stateRoot(target);
|
|
190
|
+
if (!(await pathExists(root)))
|
|
191
|
+
return false;
|
|
192
|
+
for (const marker of V2_MARKERS) {
|
|
193
|
+
if (await pathExists(join(root, marker)))
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
if (await pathExists(join(root, TRANSITIONAL_DOCS_STATE)))
|
|
197
|
+
return true;
|
|
198
|
+
return (await legacyCommandRunNames(root)).length > 0;
|
|
199
|
+
}
|
|
200
|
+
async function detectV2State(target) {
|
|
201
|
+
const root = stateRoot(target);
|
|
202
|
+
if (!(await pathExists(root)))
|
|
203
|
+
return false;
|
|
204
|
+
for (const marker of V2_MARKERS) {
|
|
205
|
+
if (await pathExists(join(root, marker)))
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
async function legacyCommandRunNames(root) {
|
|
211
|
+
const commandsRoot = join(root, "commands");
|
|
212
|
+
if (!(await pathExists(commandsRoot)))
|
|
213
|
+
return [];
|
|
214
|
+
const entries = await readdir(commandsRoot, { withFileTypes: true });
|
|
215
|
+
return entries
|
|
216
|
+
.filter((entry) => entry.isDirectory() && COMMAND_RUN_RE.test(entry.name))
|
|
217
|
+
.map((entry) => entry.name)
|
|
218
|
+
.sort();
|
|
219
|
+
}
|
|
220
|
+
export async function planMigration(target) {
|
|
221
|
+
const resolvedTarget = resolve(target);
|
|
222
|
+
const root = stateRoot(resolvedTarget);
|
|
223
|
+
const v2Detected = await detectV2State(resolvedTarget);
|
|
224
|
+
const transitionalDetected = (await pathExists(join(root, TRANSITIONAL_DOCS_STATE))) ||
|
|
225
|
+
(await legacyCommandRunNames(root)).length > 0;
|
|
226
|
+
const sourceLayout = v2Detected
|
|
227
|
+
? "v2"
|
|
228
|
+
: transitionalDetected
|
|
229
|
+
? "transitional-v3"
|
|
230
|
+
: "none";
|
|
231
|
+
const legacyDetected = sourceLayout !== "none";
|
|
232
|
+
const blockers = [];
|
|
233
|
+
const actions = [];
|
|
234
|
+
const changes = [];
|
|
235
|
+
if (!legacyDetected) {
|
|
236
|
+
return {
|
|
237
|
+
target: resolvedTarget,
|
|
238
|
+
legacyDetected,
|
|
239
|
+
sourceLayout,
|
|
240
|
+
actions,
|
|
241
|
+
blockers,
|
|
242
|
+
changes,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
if (!(await pathExists(root))) {
|
|
246
|
+
blockers.push("Missing legacy state root: " + root);
|
|
247
|
+
return {
|
|
248
|
+
target: resolvedTarget,
|
|
249
|
+
legacyDetected,
|
|
250
|
+
sourceLayout,
|
|
251
|
+
actions,
|
|
252
|
+
blockers,
|
|
253
|
+
changes,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
if (sourceLayout === "transitional-v3") {
|
|
257
|
+
const oldState = join(root, TRANSITIONAL_DOCS_STATE);
|
|
258
|
+
const newState = join(root, "commands", "docs-sync", "state.json");
|
|
259
|
+
if (await pathExists(oldState)) {
|
|
260
|
+
await readLegacyDocsSyncState(oldState, blockers);
|
|
261
|
+
if (await pathExists(newState)) {
|
|
262
|
+
blockers.push("Docs-sync state destination already exists: " + newState);
|
|
263
|
+
}
|
|
264
|
+
actions.push({
|
|
265
|
+
kind: "move-docs-sync-state",
|
|
266
|
+
from: ".speculo/" + TRANSITIONAL_DOCS_STATE,
|
|
267
|
+
to: ".speculo/commands/docs-sync/state.json",
|
|
268
|
+
detail: "Move the transitional v3 baseline into its command namespace",
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
for (const name of await legacyCommandRunNames(root)) {
|
|
272
|
+
const destination = join(root, "commands", "_legacy", name);
|
|
273
|
+
if (await pathExists(destination)) {
|
|
274
|
+
blockers.push("Legacy command report destination exists: " + destination);
|
|
275
|
+
}
|
|
276
|
+
actions.push({
|
|
277
|
+
kind: "preserve-command-report",
|
|
278
|
+
from: ".speculo/commands/" + name,
|
|
279
|
+
to: ".speculo/commands/_legacy/" + name,
|
|
280
|
+
detail: "Preserve the old report directory without rewriting history",
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
target: resolvedTarget,
|
|
285
|
+
legacyDetected,
|
|
286
|
+
sourceLayout,
|
|
287
|
+
actions,
|
|
288
|
+
blockers,
|
|
289
|
+
changes,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const rootEntries = await readdir(root);
|
|
293
|
+
for (const entry of rootEntries) {
|
|
294
|
+
if (!LEGACY_ROOT_ENTRIES.has(entry)) {
|
|
295
|
+
blockers.push("Unknown legacy state entry requires manual handling: " + join(root, entry));
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
for (const workflow of ["dev", "doc", "person"]) {
|
|
299
|
+
await validateLegacyIndex(join(root, workflow + "-status.json"), blockers);
|
|
300
|
+
}
|
|
301
|
+
await readLegacyDocsSyncState(join(root, "dev", "docs-sync-state.json"), blockers);
|
|
302
|
+
for (const workflow of ["dev", "doc", "person"]) {
|
|
303
|
+
changes.push(...await collectActiveChanges(root, workflow, blockers));
|
|
304
|
+
changes.push(...await collectArchivedChanges(root, workflow, blockers));
|
|
305
|
+
}
|
|
306
|
+
const destinations = new Set();
|
|
307
|
+
for (const change of changes) {
|
|
308
|
+
if (destinations.has(change.destinationRelative)) {
|
|
309
|
+
blockers.push("Migration destination collision: " + change.destinationRelative);
|
|
310
|
+
}
|
|
311
|
+
destinations.add(change.destinationRelative);
|
|
312
|
+
actions.push({
|
|
313
|
+
kind: change.archived ? "archive-change" : "preserve-active-change",
|
|
314
|
+
from: relative(installRoot(resolvedTarget), change.sourcePath),
|
|
315
|
+
to: join(".speculo", change.destinationRelative),
|
|
316
|
+
detail: change.sourceWorkflow + "/" + change.originalName,
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (await pathExists(join(root, ".config"))) {
|
|
320
|
+
actions.push({
|
|
321
|
+
kind: "move-config",
|
|
322
|
+
from: ".speculo/.config",
|
|
323
|
+
to: ".speculo/matt-pocock/.config",
|
|
324
|
+
detail: "Move shared v2 configuration into the Matt workflow",
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
if (await pathExists(join(root, "dev", "docs-sync-state.json"))) {
|
|
328
|
+
actions.push({
|
|
329
|
+
kind: "move-docs-sync-state",
|
|
330
|
+
from: ".speculo/dev/docs-sync-state.json",
|
|
331
|
+
to: ".speculo/commands/docs-sync/state.json",
|
|
332
|
+
detail: "Move docs-sync baseline to the global command namespace",
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
for (const legacyPath of [
|
|
336
|
+
"workflows/dev",
|
|
337
|
+
"workflows/doc",
|
|
338
|
+
"vendor/codebase-design",
|
|
339
|
+
"vendor/resolving-merge-conflicts",
|
|
340
|
+
]) {
|
|
341
|
+
if (await pathExists(join(installRoot(resolvedTarget), legacyPath))) {
|
|
342
|
+
actions.push({
|
|
343
|
+
kind: "remove-legacy-asset",
|
|
344
|
+
from: legacyPath,
|
|
345
|
+
detail: "Remove framework-managed v2 asset after state migration",
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
target: resolvedTarget,
|
|
351
|
+
legacyDetected,
|
|
352
|
+
sourceLayout,
|
|
353
|
+
actions,
|
|
354
|
+
blockers,
|
|
355
|
+
changes,
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
async function copyDirectoryContents(source, destination) {
|
|
359
|
+
if (!(await pathExists(source)))
|
|
360
|
+
return;
|
|
361
|
+
await mkdir(destination, { recursive: true });
|
|
362
|
+
const entries = await readdir(source, { withFileTypes: true });
|
|
363
|
+
for (const entry of entries) {
|
|
364
|
+
await cp(join(source, entry.name), join(destination, entry.name), {
|
|
365
|
+
recursive: entry.isDirectory(),
|
|
366
|
+
force: true,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
function migratedStatus(change, destinationName, destinationRelative, now) {
|
|
371
|
+
const status = { ...change.status };
|
|
372
|
+
delete status.category;
|
|
373
|
+
status.schema_version = 1;
|
|
374
|
+
status.workflow = change.sourceWorkflow === "person" ? "person" : "matt-pocock";
|
|
375
|
+
status.name = destinationName;
|
|
376
|
+
status.updated_at = now;
|
|
377
|
+
if (change.archived) {
|
|
378
|
+
status.change_status = "archived";
|
|
379
|
+
status.current_phase = "migration-archive";
|
|
380
|
+
status.archived = true;
|
|
381
|
+
status.archive_path = "speculo/.speculo/" +
|
|
382
|
+
destinationRelative.replaceAll("\\", "/");
|
|
383
|
+
if (change.sourceWorkflow !== "person") {
|
|
384
|
+
status.legacy_source = {
|
|
385
|
+
workflow: change.sourceWorkflow,
|
|
386
|
+
original_name: change.originalName,
|
|
387
|
+
original_status: change.status.change_status ?? "unknown",
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
const history = Array.isArray(status.phase_history)
|
|
391
|
+
? [...status.phase_history]
|
|
392
|
+
: [];
|
|
393
|
+
history.push({
|
|
394
|
+
phase: "migration-archive",
|
|
395
|
+
entered_at: now,
|
|
396
|
+
completed_at: now,
|
|
397
|
+
status: "completed",
|
|
398
|
+
});
|
|
399
|
+
status.phase_history = history;
|
|
400
|
+
}
|
|
401
|
+
return status;
|
|
402
|
+
}
|
|
403
|
+
function migratedDocsSyncState(legacy) {
|
|
404
|
+
const tracked = Array.isArray(legacy.tracked_assets)
|
|
405
|
+
? legacy.tracked_assets
|
|
406
|
+
: Array.isArray(legacy.tracked_docs) ? legacy.tracked_docs : [];
|
|
407
|
+
const synced = Array.isArray(legacy.synced_assets)
|
|
408
|
+
? legacy.synced_assets
|
|
409
|
+
: Array.isArray(legacy.synced_docs) ? legacy.synced_docs : [];
|
|
410
|
+
const lastSyncSha = typeof legacy.last_sync_sha === "string"
|
|
411
|
+
? legacy.last_sync_sha
|
|
412
|
+
: null;
|
|
413
|
+
const previousSyncSha = typeof legacy.previous_sync_sha === "string"
|
|
414
|
+
? legacy.previous_sync_sha
|
|
415
|
+
: null;
|
|
416
|
+
const totalSyncs = typeof legacy.total_syncs === "number" &&
|
|
417
|
+
Number.isInteger(legacy.total_syncs) &&
|
|
418
|
+
legacy.total_syncs >= 0
|
|
419
|
+
? legacy.total_syncs
|
|
420
|
+
: 0;
|
|
421
|
+
return {
|
|
422
|
+
schema_version: 4,
|
|
423
|
+
command: "docs-sync",
|
|
424
|
+
state_path: "speculo/.speculo/commands/docs-sync/state.json",
|
|
425
|
+
baseline: {
|
|
426
|
+
mode: "explicit",
|
|
427
|
+
sha: lastSyncSha,
|
|
428
|
+
},
|
|
429
|
+
last_range: {
|
|
430
|
+
from_sha: previousSyncSha,
|
|
431
|
+
to_sha: lastSyncSha,
|
|
432
|
+
},
|
|
433
|
+
project_targets: [],
|
|
434
|
+
pending_legacy_targets: [...new Set(tracked)],
|
|
435
|
+
scope_revision: 0,
|
|
436
|
+
scope_confirmed_at: null,
|
|
437
|
+
last_sync_run_at: typeof legacy.last_sync_run_at === "string"
|
|
438
|
+
? legacy.last_sync_run_at
|
|
439
|
+
: null,
|
|
440
|
+
total_syncs: totalSyncs,
|
|
441
|
+
synced_assets: synced,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function reportMarkdown(plan, now) {
|
|
445
|
+
const lines = [
|
|
446
|
+
"# Speculo v3 Migration Report",
|
|
447
|
+
"",
|
|
448
|
+
"- Generated: " + now,
|
|
449
|
+
"- Target: " + plan.target,
|
|
450
|
+
"- Source layout: " + plan.sourceLayout,
|
|
451
|
+
"- Actions: " + plan.actions.length,
|
|
452
|
+
"",
|
|
453
|
+
"## Actions",
|
|
454
|
+
"",
|
|
455
|
+
];
|
|
456
|
+
for (const action of plan.actions) {
|
|
457
|
+
lines.push("- " + action.kind + ": " +
|
|
458
|
+
(action.from ? action.from : "") +
|
|
459
|
+
(action.to ? " -> " + action.to : "") +
|
|
460
|
+
" — " + action.detail);
|
|
461
|
+
}
|
|
462
|
+
lines.push("", "## Follow-up", "", "Run `speculo init <target>` and select matt-pocock/person as needed.", "Legacy dev/doc active work is historical archive and is not resumed automatically.", "");
|
|
463
|
+
return lines.join("\n");
|
|
464
|
+
}
|
|
465
|
+
async function preserveLegacyCommandEntries(source, destination) {
|
|
466
|
+
if (!(await pathExists(source)))
|
|
467
|
+
return;
|
|
468
|
+
const entries = await readdir(source, { withFileTypes: true });
|
|
469
|
+
for (const entry of entries) {
|
|
470
|
+
if (entry.name === ".config" || entry.name === "_legacy")
|
|
471
|
+
continue;
|
|
472
|
+
await mkdir(destination, { recursive: true });
|
|
473
|
+
await cp(join(source, entry.name), join(destination, entry.name), {
|
|
474
|
+
recursive: entry.isDirectory(),
|
|
475
|
+
force: false,
|
|
476
|
+
errorOnExist: true,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
await copyDirectoryContents(join(source, "_legacy"), destination);
|
|
480
|
+
}
|
|
481
|
+
async function writeMigrationReport(stage, plan, now) {
|
|
482
|
+
const reportRoot = join(stage, "commands", "migrate");
|
|
483
|
+
await mkdir(reportRoot, { recursive: true });
|
|
484
|
+
const stem = now.slice(0, 10) + "-workspace-layout-v3";
|
|
485
|
+
let reportPath = join(reportRoot, stem + ".md");
|
|
486
|
+
let suffix = 2;
|
|
487
|
+
while (await pathExists(reportPath)) {
|
|
488
|
+
reportPath = join(reportRoot, stem + "-" + String(suffix).padStart(2, "0") + ".md");
|
|
489
|
+
suffix += 1;
|
|
490
|
+
}
|
|
491
|
+
await writeFile(reportPath, reportMarkdown(plan, now));
|
|
492
|
+
}
|
|
493
|
+
async function buildMigratedState(plan, packageRoot, stage) {
|
|
494
|
+
const root = stateRoot(plan.target);
|
|
495
|
+
const now = new Date().toISOString();
|
|
496
|
+
const templateRoot = join(packageRoot, "template");
|
|
497
|
+
if (plan.sourceLayout === "transitional-v3") {
|
|
498
|
+
await cp(root, stage, { recursive: true, force: true });
|
|
499
|
+
const workspaceDestination = join(stage, "workspace.json");
|
|
500
|
+
if (!(await pathExists(workspaceDestination))) {
|
|
501
|
+
await cp(join(templateRoot, ".speculo", "workspace.json"), workspaceDestination, { force: false, errorOnExist: true });
|
|
502
|
+
}
|
|
503
|
+
const commandsRoot = join(stage, "commands");
|
|
504
|
+
const legacyRoot = join(commandsRoot, "_legacy");
|
|
505
|
+
for (const name of await legacyCommandRunNames(stage)) {
|
|
506
|
+
await mkdir(legacyRoot, { recursive: true });
|
|
507
|
+
await rename(join(commandsRoot, name), join(legacyRoot, name));
|
|
508
|
+
}
|
|
509
|
+
const oldState = join(stage, TRANSITIONAL_DOCS_STATE);
|
|
510
|
+
if (await pathExists(oldState)) {
|
|
511
|
+
const legacy = JSON.parse(await readFile(oldState, "utf8"));
|
|
512
|
+
const destination = join(commandsRoot, "docs-sync", "state.json");
|
|
513
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
514
|
+
await writeFile(destination, JSON.stringify(migratedDocsSyncState(legacy), null, 2) + "\n");
|
|
515
|
+
await rm(oldState, { force: true });
|
|
516
|
+
const oldConfigDir = dirname(oldState);
|
|
517
|
+
if ((await readdir(oldConfigDir)).length === 0) {
|
|
518
|
+
await rm(oldConfigDir, { recursive: true, force: true });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
await writeMigrationReport(stage, plan, now);
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
await cp(join(templateRoot, ".speculo"), stage, {
|
|
525
|
+
recursive: true,
|
|
526
|
+
force: true,
|
|
527
|
+
});
|
|
528
|
+
await cp(join(templateRoot, "workflows", "matt-pocock", "_state"), join(stage, "matt-pocock"), { recursive: true, force: true });
|
|
529
|
+
await cp(join(templateRoot, "workflows", "person", "_state"), join(stage, "person"), { recursive: true, force: true });
|
|
530
|
+
await preserveLegacyCommandEntries(join(root, "commands"), join(stage, "commands", "_legacy"));
|
|
531
|
+
await copyDirectoryContents(join(root, ".config"), join(stage, "matt-pocock", ".config"));
|
|
532
|
+
const docsSyncSource = join(root, "dev", "docs-sync-state.json");
|
|
533
|
+
if (await pathExists(docsSyncSource)) {
|
|
534
|
+
const legacyDocsSync = JSON.parse(await readFile(docsSyncSource, "utf8"));
|
|
535
|
+
const docsSync = migratedDocsSyncState(legacyDocsSync);
|
|
536
|
+
const destination = join(stage, "commands", "docs-sync", "state.json");
|
|
537
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
538
|
+
await writeFile(destination, JSON.stringify(docsSync, null, 2) + "\n");
|
|
539
|
+
}
|
|
540
|
+
const personActive = [];
|
|
541
|
+
for (const change of plan.changes) {
|
|
542
|
+
const destination = join(stage, change.destinationRelative);
|
|
543
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
544
|
+
await cp(change.sourcePath, destination, {
|
|
545
|
+
recursive: true,
|
|
546
|
+
force: false,
|
|
547
|
+
errorOnExist: true,
|
|
548
|
+
});
|
|
549
|
+
const destinationName = basename(destination);
|
|
550
|
+
const status = migratedStatus(change, destinationName, change.destinationRelative, now);
|
|
551
|
+
await writeFile(join(destination, ".status.json"), JSON.stringify(status, null, 2) + "\n");
|
|
552
|
+
if (change.sourceWorkflow === "person" &&
|
|
553
|
+
!change.archived &&
|
|
554
|
+
status.change_status === "active") {
|
|
555
|
+
personActive.push({
|
|
556
|
+
name: destinationName,
|
|
557
|
+
current_phase: status.current_phase ?? "00-init",
|
|
558
|
+
updated_at: status.updated_at,
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
await writeFile(join(stage, "person", "status.json"), JSON.stringify({ schema_version: 1, workflow: "person", active: personActive }, null, 2) + "\n");
|
|
563
|
+
await writeMigrationReport(stage, plan, now);
|
|
564
|
+
}
|
|
565
|
+
async function moveLegacyAssetsToBackup(root, backup) {
|
|
566
|
+
const moved = [];
|
|
567
|
+
for (const relativePath of [
|
|
568
|
+
"workflows/dev",
|
|
569
|
+
"workflows/doc",
|
|
570
|
+
"vendor/codebase-design",
|
|
571
|
+
"vendor/resolving-merge-conflicts",
|
|
572
|
+
]) {
|
|
573
|
+
const source = join(root, relativePath);
|
|
574
|
+
if (!(await pathExists(source)))
|
|
575
|
+
continue;
|
|
576
|
+
const destination = join(backup, relativePath);
|
|
577
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
578
|
+
await rename(source, destination);
|
|
579
|
+
moved.push({ source, backup: destination });
|
|
580
|
+
}
|
|
581
|
+
return moved;
|
|
582
|
+
}
|
|
583
|
+
async function restoreLegacyAssets(moved) {
|
|
584
|
+
for (const item of [...moved].reverse()) {
|
|
585
|
+
if (!(await pathExists(item.backup)))
|
|
586
|
+
continue;
|
|
587
|
+
await mkdir(dirname(item.source), { recursive: true });
|
|
588
|
+
await rename(item.backup, item.source);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
export async function migrateSpeculo(targetArg = ".", options = {}) {
|
|
592
|
+
const target = resolve(targetArg);
|
|
593
|
+
const packageRoot = resolve(options.packageRoot ?? process.cwd());
|
|
594
|
+
const plan = await planMigration(target);
|
|
595
|
+
if (plan.blockers.length > 0) {
|
|
596
|
+
throw new Error(["Speculo migration blocked:", ...plan.blockers.map((item) => "- " + item)]
|
|
597
|
+
.join("\n"));
|
|
598
|
+
}
|
|
599
|
+
if (!plan.legacyDetected || !options.apply) {
|
|
600
|
+
return {
|
|
601
|
+
target,
|
|
602
|
+
legacyDetected: plan.legacyDetected,
|
|
603
|
+
applied: false,
|
|
604
|
+
actions: plan.actions,
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
const root = installRoot(target);
|
|
608
|
+
const state = stateRoot(target);
|
|
609
|
+
const stage = join(root, ".speculo-migrate-stage");
|
|
610
|
+
const backup = join(root, ".speculo-migrate-backup");
|
|
611
|
+
const assetBackup = join(root, ".speculo-migrate-assets");
|
|
612
|
+
await rm(stage, { recursive: true, force: true });
|
|
613
|
+
await rm(backup, { recursive: true, force: true });
|
|
614
|
+
await rm(assetBackup, { recursive: true, force: true });
|
|
615
|
+
await buildMigratedState(plan, packageRoot, stage);
|
|
616
|
+
let stateMoved = false;
|
|
617
|
+
let newStateInstalled = false;
|
|
618
|
+
let movedAssets = [];
|
|
619
|
+
try {
|
|
620
|
+
movedAssets = await moveLegacyAssetsToBackup(root, assetBackup);
|
|
621
|
+
await rename(state, backup);
|
|
622
|
+
stateMoved = true;
|
|
623
|
+
await rename(stage, state);
|
|
624
|
+
newStateInstalled = true;
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
if (newStateInstalled && (await pathExists(state))) {
|
|
628
|
+
await rm(state, { recursive: true, force: true });
|
|
629
|
+
}
|
|
630
|
+
if (stateMoved && (await pathExists(backup))) {
|
|
631
|
+
await rename(backup, state);
|
|
632
|
+
}
|
|
633
|
+
await restoreLegacyAssets(movedAssets);
|
|
634
|
+
await rm(stage, { recursive: true, force: true });
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
637
|
+
await rm(backup, { recursive: true, force: true });
|
|
638
|
+
await rm(assetBackup, { recursive: true, force: true });
|
|
639
|
+
return {
|
|
640
|
+
target,
|
|
641
|
+
legacyDetected: true,
|
|
642
|
+
applied: true,
|
|
643
|
+
actions: plan.actions,
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
//# sourceMappingURL=migrate.js.map
|