@ikon85/agent-workflow-kit 0.16.4 → 0.17.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 +5 -0
- package/agent-workflow-kit.package.json +15 -1
- package/docs/research/wave-43-script-hook-census.md +128 -0
- package/package.json +1 -1
- package/src/lib/bundle.mjs +4 -0
- package/src/lib/release-preview.mjs +114 -0
- package/src/lib/semver.mjs +25 -0
package/README.md
CHANGED
|
@@ -332,6 +332,11 @@ still reference. Flags: `--force` (overwrite pre-existing files on `init`),
|
|
|
332
332
|
|
|
333
333
|
## Release notes
|
|
334
334
|
|
|
335
|
+
### 0.17.0
|
|
336
|
+
|
|
337
|
+
- added: `src/lib/release-preview.mjs`
|
|
338
|
+
- added: `src/lib/semver.mjs`
|
|
339
|
+
|
|
335
340
|
### 0.16.4
|
|
336
341
|
|
|
337
342
|
- Metadata-only release.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"kitVersion": "0.
|
|
2
|
+
"kitVersion": "0.17.0",
|
|
3
3
|
"files": [
|
|
4
4
|
{
|
|
5
5
|
"path": ".agents/skills/ask-matt/SKILL.md",
|
|
@@ -2069,6 +2069,20 @@
|
|
|
2069
2069
|
"sha256": "24ce9373c464cc8363c53b0c690c3231cbf11362e6e08336e0e97663579d1f19",
|
|
2070
2070
|
"mode": 493,
|
|
2071
2071
|
"origin": "kit"
|
|
2072
|
+
},
|
|
2073
|
+
{
|
|
2074
|
+
"path": "src/lib/release-preview.mjs",
|
|
2075
|
+
"kind": "script",
|
|
2076
|
+
"sha256": "a4e6627fac2d62812e44cfcb80c46539563734e7fa11f94b3dda37ff6fe6ae8f",
|
|
2077
|
+
"mode": 420,
|
|
2078
|
+
"origin": "kit"
|
|
2079
|
+
},
|
|
2080
|
+
{
|
|
2081
|
+
"path": "src/lib/semver.mjs",
|
|
2082
|
+
"kind": "script",
|
|
2083
|
+
"sha256": "7d0a8da3740ed1eb79edb80f0eedb3cd1de47692d4886c6b119b39073a6468d1",
|
|
2084
|
+
"mode": 420,
|
|
2085
|
+
"origin": "kit"
|
|
2072
2086
|
}
|
|
2073
2087
|
]
|
|
2074
2088
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Wave 43 script and hook census
|
|
2
|
+
|
|
3
|
+
Verified on 2026-07-15 against `agent-workflow-kit` commit
|
|
4
|
+
`2d47dce23d9204b44951334c3c370bdb997c0943` and Testreporter commit
|
|
5
|
+
[`458747ebe8d67f66840cb3c1e75284cf2be7fb20`](https://github.com/iKon85/Testreporter/tree/458747ebe8d67f66840cb3c1e75284cf2be7fb20).
|
|
6
|
+
This is the research input for issue #59. It records facts only; it does not make
|
|
7
|
+
ship, opt-in, profile-seam, or maintenance decisions.
|
|
8
|
+
|
|
9
|
+
## Method and reconciliation
|
|
10
|
+
|
|
11
|
+
The public-repository denominator is every tracked file below `scripts/**` and
|
|
12
|
+
`.claude/hooks/**`, excluding `test_*.py`, `*.test.mjs`, and the metadata file
|
|
13
|
+
`scripts/.npmignore`. The recount is:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
68 tracked entries
|
|
17
|
+
- 31 tests
|
|
18
|
+
- 1 metadata file
|
|
19
|
+
= 36 current script/hook entries
|
|
20
|
+
= 32 scripts + 4 hooks
|
|
21
|
+
= 30 shipped helpers + 6 maintainer-only helpers
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The shipped/helper split is derived from
|
|
25
|
+
[`src/lib/bundle.mjs`](../../src/lib/bundle.mjs), whose `HELPER_FILES` list is
|
|
26
|
+
the public distribution authority. The historical input is the 26-candidate
|
|
27
|
+
list in [Testreporter #1976](https://github.com/iKon85/Testreporter/issues/1976),
|
|
28
|
+
originally reconciled as 11 portable plus 15 generalize-first candidates in
|
|
29
|
+
[Testreporter #1964](https://github.com/iKon85/Testreporter/issues/1964).
|
|
30
|
+
|
|
31
|
+
Fresh exact-path comparison:
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
Historical candidates: 26/26 accounted for
|
|
35
|
+
Still present at the exact Testreporter path: 26/26
|
|
36
|
+
Present at the exact public-repo path: 0/26
|
|
37
|
+
Proven exact rename in the public repo: 0/26
|
|
38
|
+
Explicitly retired by a locked decision: 0/26
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Therefore every historical row is classified as **present in the current
|
|
42
|
+
consumer and absent from the current public denominator**. Related public
|
|
43
|
+
primitives are called out only as inputs to the later grill; similarity is not
|
|
44
|
+
treated as proof of a rename or as a ship decision.
|
|
45
|
+
|
|
46
|
+
## Fresh public denominator: 36 of 36
|
|
47
|
+
|
|
48
|
+
| Current path | Distribution status |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `.claude/hooks/_hook_utils.py` | shipped helper |
|
|
51
|
+
| `.claude/hooks/drift-guard.py` | shipped helper |
|
|
52
|
+
| `.claude/hooks/skill-drift-hint.py` | shipped helper |
|
|
53
|
+
| `.claude/hooks/sync-board-status.py` | shipped helper |
|
|
54
|
+
| `scripts/anchor_table.py` | shipped helper |
|
|
55
|
+
| `scripts/board-sync.py` | shipped helper |
|
|
56
|
+
| `scripts/board_config.py` | shipped helper |
|
|
57
|
+
| `scripts/board_fields.py` | shipped helper |
|
|
58
|
+
| `scripts/build-kit.mjs` | maintainer-only |
|
|
59
|
+
| `scripts/census/delta.mjs` | shipped helper |
|
|
60
|
+
| `scripts/census/fingerprint.mjs` | shipped helper |
|
|
61
|
+
| `scripts/census/index.mjs` | shipped helper |
|
|
62
|
+
| `scripts/census/scan.mjs` | shipped helper |
|
|
63
|
+
| `scripts/census/state.mjs` | shipped helper |
|
|
64
|
+
| `scripts/census/transaction.mjs` | shipped helper |
|
|
65
|
+
| `scripts/check-kit-staleness.mjs` | maintainer-only |
|
|
66
|
+
| `scripts/execute-ready-check.py` | shipped helper |
|
|
67
|
+
| `scripts/grill-census-wiring-guard.mjs` | maintainer-only |
|
|
68
|
+
| `scripts/issue_deps.py` | shipped helper |
|
|
69
|
+
| `scripts/kit-release.mjs` | shipped helper |
|
|
70
|
+
| `scripts/kit-update-pr.mjs` | shipped helper |
|
|
71
|
+
| `scripts/lib/audit-refs.mjs` | maintainer-only |
|
|
72
|
+
| `scripts/lib/scrub.mjs` | maintainer-only |
|
|
73
|
+
| `scripts/loc_offender_core.py` | shipped helper |
|
|
74
|
+
| `scripts/loc_offender_gate.py` | shipped helper |
|
|
75
|
+
| `scripts/node_kind.py` | shipped helper |
|
|
76
|
+
| `scripts/portability_profile_scan.py` | maintainer-only |
|
|
77
|
+
| `scripts/pr-body-check.py` | shipped helper |
|
|
78
|
+
| `scripts/program_graph.py` | shipped helper |
|
|
79
|
+
| `scripts/program_graph_parse.py` | shipped helper |
|
|
80
|
+
| `scripts/program_graph_validate.py` | shipped helper |
|
|
81
|
+
| `scripts/program_sync.py` | shipped helper |
|
|
82
|
+
| `scripts/release-delta-guard.mjs` | shipped helper |
|
|
83
|
+
| `scripts/release-parity.mjs` | shipped helper |
|
|
84
|
+
| `scripts/release-state.mjs` | shipped helper |
|
|
85
|
+
| `scripts/wrapup-land.py` | shipped helper |
|
|
86
|
+
|
|
87
|
+
## Historical candidate crosswalk: 26 of 26
|
|
88
|
+
|
|
89
|
+
All Testreporter links below are pinned to the verified consumer commit. “No
|
|
90
|
+
dedicated tracked test found” means the fresh tracked-tree search found no test
|
|
91
|
+
named for that candidate; it is not a statement about runtime correctness.
|
|
92
|
+
|
|
93
|
+
| Historical candidate | Current public status and adjacent evidence | Current consumer dependency | Existing consumer test evidence |
|
|
94
|
+
|---|---|---|---|
|
|
95
|
+
| `scripts/audit_gate.py` | Exact path absent. | [`package.json#L44`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/package.json#L44) and `.githooks/pre-push` invoke it. | [`scripts/test_audit_gate.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/scripts/test_audit_gate.py), [`scripts/test_pre_push_audit_gate.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/scripts/test_pre_push_audit_gate.py) |
|
|
96
|
+
| `scripts/cleanup-worktrees.sh` | Exact path absent. Public `scripts/wrapup-land.py` has adjacent worktree-cleanup behavior, not a proven rename. | No separate tracked consumer found beyond direct/manual use. | No dedicated tracked test found. |
|
|
97
|
+
| `scripts/install-git-hooks.sh` | Exact path absent. | [`package.json#L45`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/package.json#L45) invokes it during postinstall. | [`scripts/test_install_git_hooks.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/scripts/test_install_git_hooks.py) |
|
|
98
|
+
| `scripts/ensure-gitleaks.sh` | Exact path absent. | [`package.json#L45`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/package.json#L45) invokes it during postinstall; `docs/security/secret-scan.md` documents it. | No dedicated tracked test found. |
|
|
99
|
+
| `.claude/hooks/block-bg-double-background.py` | Exact path absent. | [`.claude/settings.json#L85`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L85) wires it. | [`.claude/hooks/test_block_bg_double_background.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_block_bg_double_background.py) |
|
|
100
|
+
| `.claude/hooks/block-npm-install-in-pnpm.py` | Exact path absent. | [`.claude/settings.json#L75`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L75) wires it. | [`.claude/hooks/test_block_npm_install_in_pnpm.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_block_npm_install_in_pnpm.py) |
|
|
101
|
+
| `.claude/hooks/block-secrets.py` | Exact path absent. | [`.claude/settings.json#L56-L65`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L56-L65) wires it at two hook events. | [`.claude/hooks/test_block_secrets_smoke.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_block_secrets_smoke.py) |
|
|
102
|
+
| `.claude/hooks/branch-watch.py` | Exact path absent. | [`.claude/settings.json#L161`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L161) wires it. | [`.claude/hooks/test_branch_watch_smoke.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_branch_watch_smoke.py) |
|
|
103
|
+
| `.claude/hooks/grep-shim-guard.py` | Exact path absent. | [`.claude/settings.json#L80`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L80) wires it. | [`.claude/hooks/test_grep_shim_guard.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_grep_shim_guard.py) |
|
|
104
|
+
| `.claude/hooks/recon-size-hint.py` | Exact path absent. | [`.claude/settings.json#L134`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L134) wires it. | [`.claude/hooks/test_recon_size_hint.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_recon_size_hint.py) |
|
|
105
|
+
| `.claude/hooks/loc-offender-forewarn.py` | Exact path absent. Public `scripts/loc_offender_core.py` and `scripts/loc_offender_gate.py` are related shipped primitives, not a proven rename. | [`.claude/settings.json#L18`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L18) wires it. | [`scripts/test_loc_offender_forewarn.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/scripts/test_loc_offender_forewarn.py) |
|
|
106
|
+
| `scripts/bump-version.sh` | Exact path absent. Public `scripts/kit-release.mjs` contains tested SemVer and metadata-update behavior, not a proven rename. | No separate consumer wiring found; the script is directly invoked. | No dedicated tracked test found for the historical script. |
|
|
107
|
+
| `scripts/consolidate-memories.sh` | Exact path absent. | The two historical memory templates name it as their consolidator. | No dedicated tracked test found. |
|
|
108
|
+
| `scripts/memory-templates/meta_decision_layer_choice.md` | Exact path absent. | Input consumed by `scripts/consolidate-memories.sh`. | No dedicated tracked test found. |
|
|
109
|
+
| `scripts/memory-templates/meta_memory_lifecycle.md` | Exact path absent. | Input consumed by `scripts/consolidate-memories.sh`. | No dedicated tracked test found. |
|
|
110
|
+
| `scripts/setup-worktree.sh` | Exact path absent. Public workflow prose allows a project helper or plain `git worktree add`; that is not a rename. | Central Testreporter worktree, port, and local-env setup primitive referenced by workflow documentation. | [`scripts/setup-worktree.test.mjs`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/scripts/setup-worktree.test.mjs), [`scripts/test_pre_push_loc_gate.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/scripts/test_pre_push_loc_gate.py) |
|
|
111
|
+
| `.claude/hooks/baseline-capture-hint.py` | Exact path absent. | [`.claude/settings.json#L114`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L114) wires it. | [`.claude/hooks/test_baseline_capture_hint_smoke.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_baseline_capture_hint_smoke.py) |
|
|
112
|
+
| `.claude/hooks/branch-context.py` | Exact path absent. | [`.claude/settings.json#L13`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L13) wires it. | [`.claude/hooks/test_branch_context_smoke.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_branch_context_smoke.py) |
|
|
113
|
+
| `.claude/hooks/convention-drift-hint.py` | Exact path absent. Public `.claude/hooks/skill-drift-hint.py` and `drift-guard.py` are narrower adjacent mechanisms, not proven renames. | [`.claude/settings.json#L33`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L33) and rule `SOURCES.txt` files refer to it. | No dedicated tracked test found. |
|
|
114
|
+
| `.claude/hooks/enforce-worktree.py` | Exact path absent. | [`.claude/settings.json#L109`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L109) wires it. | [`.claude/hooks/test_enforce_worktree.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_enforce_worktree.py) |
|
|
115
|
+
| `.claude/hooks/enforce-worktree-cwd.py` | Exact path absent. | [`.claude/settings.json#L95`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L95) wires it. | [`.claude/hooks/test_enforce_worktree_cwd_smoke.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_enforce_worktree_cwd_smoke.py) |
|
|
116
|
+
| `.claude/hooks/enforce-worktree-discipline.py` | Exact path absent. | [`.claude/settings.json#L70`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L70) wires it. | [`.claude/hooks/test_enforce_worktree_discipline_smoke.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_enforce_worktree_discipline_smoke.py) |
|
|
117
|
+
| `.claude/hooks/migration-snapshot-reminder.py` | Exact path absent. | [`.claude/settings.json#L166`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L166) wires it. | [`.claude/hooks/test_migration_snapshot_reminder_smoke.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_migration_snapshot_reminder_smoke.py) |
|
|
118
|
+
| `.claude/hooks/pre-refactor-sweep.py` | Exact path absent. | [`.claude/settings.json#L23`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L23) wires it. | [`.claude/hooks/test_pre_refactor_sweep.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_pre_refactor_sweep.py) |
|
|
119
|
+
| `.claude/hooks/typecheck-on-stop.sh` | Exact path absent. | [`.claude/settings.json#L145`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L145) wires it. | No dedicated tracked test found. |
|
|
120
|
+
| `.claude/hooks/slice-handoff-hint.py` | Exact path absent. | [`.claude/settings.json#L44`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/settings.json#L44) wires it. | [`.claude/hooks/test_slice_handoff_hint_smoke.py`](https://github.com/iKon85/Testreporter/blob/458747ebe8d67f66840cb3c1e75284cf2be7fb20/.claude/hooks/test_slice_handoff_hint_smoke.py) |
|
|
121
|
+
|
|
122
|
+
## Inputs for the decision grill
|
|
123
|
+
|
|
124
|
+
The crosswalk establishes the decision set but intentionally leaves its verdict
|
|
125
|
+
columns blank. Issue #59 must decide, with the user, each row’s outcome, opt-in,
|
|
126
|
+
profile seam, security and maintenance cost, and ship verdict. In particular,
|
|
127
|
+
the adjacent public primitives identified above must be compared behaviorally
|
|
128
|
+
before any row is called replaced, redundant, or already shipped.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ikon85/agent-workflow-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Portable AI-agent workflow skills (plan → execute → land → learn) for Claude Code & Codex — grilling, TDD, diagnosis, two-axis code review, cross-model Codex review, design & domain-modeling, plus a skill router (ask-matt). npx init/update/diff/uninstall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/lib/bundle.mjs
CHANGED
|
@@ -47,6 +47,10 @@ export const HELPER_FILES = [
|
|
|
47
47
|
// the parity primitive rather than growing a second registry/GitHub comparison.
|
|
48
48
|
{ path: 'scripts/release-parity.mjs', kind: 'script', mode: 0o644 },
|
|
49
49
|
{ path: 'scripts/release-state.mjs', kind: 'script', mode: 0o644 },
|
|
50
|
+
// Consumer-owned project release profiles use these read-only shared
|
|
51
|
+
// primitives before any apply/commit/tag action is allowed.
|
|
52
|
+
{ path: 'src/lib/semver.mjs', kind: 'script', mode: 0o644 },
|
|
53
|
+
{ path: 'src/lib/release-preview.mjs', kind: 'script', mode: 0o644 },
|
|
50
54
|
// GitHub-consumer automation: invokes the existing update command, then owns
|
|
51
55
|
// only the stable tested branch/pull-request upsert.
|
|
52
56
|
{ path: 'scripts/kit-update-pr.mjs', kind: 'script', mode: 0o755 },
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { nextVersion, parseSemver } from './semver.mjs';
|
|
5
|
+
|
|
6
|
+
export const PROJECT_RELEASE_PROFILE = 'docs/agents/workflow-capabilities.json';
|
|
7
|
+
|
|
8
|
+
export async function loadProjectReleaseProfile(consumerRoot) {
|
|
9
|
+
const profile = JSON.parse(
|
|
10
|
+
await readFile(join(consumerRoot, PROJECT_RELEASE_PROFILE), 'utf8'),
|
|
11
|
+
);
|
|
12
|
+
if (profile.schemaVersion !== 1 || !profile.projectRelease) {
|
|
13
|
+
throw new Error('project release profile must contain schemaVersion 1 and projectRelease');
|
|
14
|
+
}
|
|
15
|
+
if (!Array.isArray(profile.projectRelease.versionFiles)
|
|
16
|
+
|| profile.projectRelease.versionFiles.length === 0) {
|
|
17
|
+
throw new Error('project release profile requires at least one versionFiles entry');
|
|
18
|
+
}
|
|
19
|
+
return profile.projectRelease;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function readVersionFile(consumerRoot, path) {
|
|
23
|
+
const raw = await readFile(join(consumerRoot, path), 'utf8');
|
|
24
|
+
const body = JSON.parse(raw);
|
|
25
|
+
return {
|
|
26
|
+
path,
|
|
27
|
+
version: body.version,
|
|
28
|
+
sha256: createHash('sha256').update(raw).digest('hex'),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function previewProjectRelease(options) {
|
|
33
|
+
const {
|
|
34
|
+
consumerRoot, profile, requestedVersion,
|
|
35
|
+
repositoryFacts = { dirtyPaths: [], existingTags: [] },
|
|
36
|
+
} = options;
|
|
37
|
+
const files = await Promise.all(
|
|
38
|
+
profile.versionFiles.map((path) => readVersionFile(consumerRoot, path)),
|
|
39
|
+
);
|
|
40
|
+
const currentVersion = files[0].version;
|
|
41
|
+
const paths = files.map(({ path }) => path);
|
|
42
|
+
const invalidFiles = files.filter(({ version }) => {
|
|
43
|
+
try { parseSemver(version); return false; } catch { return true; }
|
|
44
|
+
});
|
|
45
|
+
if (invalidFiles.length) {
|
|
46
|
+
return {
|
|
47
|
+
status: 'blocked',
|
|
48
|
+
summary: {
|
|
49
|
+
currentVersion,
|
|
50
|
+
targetVersion: null,
|
|
51
|
+
packageCount: paths.length,
|
|
52
|
+
synchronizedFiles: paths,
|
|
53
|
+
},
|
|
54
|
+
blockers: [{
|
|
55
|
+
code: 'invalid-current-version',
|
|
56
|
+
files: invalidFiles.map(({ path, version }) => ({ path, version })),
|
|
57
|
+
}],
|
|
58
|
+
actions: [],
|
|
59
|
+
repositoryFacts,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
let targetVersion;
|
|
63
|
+
try {
|
|
64
|
+
targetVersion = nextVersion(currentVersion, requestedVersion);
|
|
65
|
+
} catch {
|
|
66
|
+
return {
|
|
67
|
+
status: 'blocked',
|
|
68
|
+
summary: {
|
|
69
|
+
currentVersion,
|
|
70
|
+
targetVersion: null,
|
|
71
|
+
packageCount: paths.length,
|
|
72
|
+
synchronizedFiles: paths,
|
|
73
|
+
},
|
|
74
|
+
blockers: [{ code: 'invalid-target-version', requestedVersion }],
|
|
75
|
+
actions: [],
|
|
76
|
+
repositoryFacts,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const tag = `${profile.tagPrefix ?? 'v'}${targetVersion}`;
|
|
80
|
+
const blockers = [];
|
|
81
|
+
if (files.some(({ version }) => version !== currentVersion)) {
|
|
82
|
+
blockers.push({
|
|
83
|
+
code: 'divergent-versions',
|
|
84
|
+
files: files.map(({ path, version }) => ({ path, version })),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const dirtyTargets = paths.filter((path) => repositoryFacts.dirtyPaths.includes(path));
|
|
88
|
+
if (dirtyTargets.length) blockers.push({ code: 'dirty-targets', paths: dirtyTargets });
|
|
89
|
+
if (repositoryFacts.existingTags.includes(tag)) {
|
|
90
|
+
blockers.push({ code: 'existing-tag', tag });
|
|
91
|
+
}
|
|
92
|
+
const snapshot = files.map(({ path, sha256 }) => ({ path, sha256 }));
|
|
93
|
+
const confirmation = createHash('sha256').update(JSON.stringify({
|
|
94
|
+
currentVersion, targetVersion, snapshot, tag,
|
|
95
|
+
})).digest('hex');
|
|
96
|
+
return {
|
|
97
|
+
status: blockers.length ? 'blocked' : 'ready',
|
|
98
|
+
summary: {
|
|
99
|
+
currentVersion,
|
|
100
|
+
targetVersion,
|
|
101
|
+
packageCount: paths.length,
|
|
102
|
+
synchronizedFiles: paths,
|
|
103
|
+
},
|
|
104
|
+
blockers,
|
|
105
|
+
actions: [
|
|
106
|
+
{ type: 'write-version', version: targetVersion, paths },
|
|
107
|
+
{ type: 'commit', message: `chore: prepare project release ${targetVersion}` },
|
|
108
|
+
{ type: 'tag', name: tag },
|
|
109
|
+
],
|
|
110
|
+
repositoryFacts,
|
|
111
|
+
snapshot,
|
|
112
|
+
confirmation,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
|
|
2
|
+
|
|
3
|
+
export function parseSemver(version) {
|
|
4
|
+
const match = SEMVER.exec(version);
|
|
5
|
+
if (!match) throw new Error(`invalid semver: ${version}`);
|
|
6
|
+
return match.slice(1).map(Number);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function compareSemver(left, right) {
|
|
10
|
+
const a = parseSemver(left);
|
|
11
|
+
const b = parseSemver(right);
|
|
12
|
+
for (let index = 0; index < a.length; index += 1) {
|
|
13
|
+
if (a[index] !== b[index]) return Math.sign(a[index] - b[index]);
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function nextVersion(current, requested) {
|
|
19
|
+
const [major, minor, patch] = parseSemver(current);
|
|
20
|
+
if (requested === 'major') return `${major + 1}.0.0`;
|
|
21
|
+
if (requested === 'minor') return `${major}.${minor + 1}.0`;
|
|
22
|
+
if (requested === 'patch') return `${major}.${minor}.${patch + 1}`;
|
|
23
|
+
parseSemver(requested);
|
|
24
|
+
return requested;
|
|
25
|
+
}
|