@aperant/framework 0.19.0 → 0.20.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/CHANGELOG.md +109 -0
- package/agents/apt-executor.md +6 -0
- package/agents/apt-planner.md +3 -2
- package/bin/features-reconcile-check.mjs +77 -0
- package/dist/cli/artifacts/self-stage.d.mts.map +1 -1
- package/dist/cli/artifacts/self-stage.mjs +2 -1
- package/dist/cli/artifacts/self-stage.mjs.map +1 -1
- package/dist/cli/cli-wrappers/features-reconcile.d.mts +2 -0
- package/dist/cli/cli-wrappers/features-reconcile.d.mts.map +1 -0
- package/dist/cli/cli-wrappers/features-reconcile.mjs +9 -0
- package/dist/cli/cli-wrappers/features-reconcile.mjs.map +1 -0
- package/dist/cli/commands/features-reconcile.d.mts +2 -0
- package/dist/cli/commands/features-reconcile.d.mts.map +1 -0
- package/dist/cli/commands/features-reconcile.mjs +143 -0
- package/dist/cli/commands/features-reconcile.mjs.map +1 -0
- package/dist/cli/commands/health-check.d.mts +2 -2
- package/dist/cli/commands/health-check.mjs +2 -2
- package/dist/cli/dispatch.mjs +2 -2
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/features/area-shape.d.mts +11 -0
- package/dist/cli/features/area-shape.d.mts.map +1 -0
- package/dist/cli/features/area-shape.mjs +141 -0
- package/dist/cli/features/area-shape.mjs.map +1 -0
- package/dist/cli/features/delta.d.mts +78 -0
- package/dist/cli/features/delta.d.mts.map +1 -0
- package/dist/cli/features/delta.mjs +591 -0
- package/dist/cli/features/delta.mjs.map +1 -0
- package/dist/cli/features/reconcile.d.mts +236 -0
- package/dist/cli/features/reconcile.d.mts.map +1 -0
- package/dist/cli/features/reconcile.mjs +1126 -0
- package/dist/cli/features/reconcile.mjs.map +1 -0
- package/dist/cli/features/surface-map.d.mts +63 -0
- package/dist/cli/features/surface-map.d.mts.map +1 -0
- package/dist/cli/features/surface-map.mjs +191 -0
- package/dist/cli/features/surface-map.mjs.map +1 -0
- package/dist/cli/{commands/features-audit.d.mts → features/write-root.d.mts} +4 -3
- package/dist/cli/features/write-root.d.mts.map +1 -0
- package/dist/cli/features/write-root.mjs +137 -0
- package/dist/cli/features/write-root.mjs.map +1 -0
- package/dist/cli/gate/gates/features-reconciled.d.mts +36 -0
- package/dist/cli/gate/gates/features-reconciled.d.mts.map +1 -0
- package/dist/cli/gate/gates/features-reconciled.mjs +67 -0
- package/dist/cli/gate/gates/features-reconciled.mjs.map +1 -0
- package/dist/cli/gate/gates/index.mjs +1 -0
- package/dist/cli/gate/gates/index.mjs.map +1 -1
- package/dist/cli/help.mjs +1 -1
- package/dist/cli/release-notes/ship-autodraft.mjs +2 -2
- package/dist/cli/variant/gallery.d.mts.map +1 -1
- package/dist/cli/variant/gallery.mjs +7 -3
- package/dist/cli/variant/gallery.mjs.map +1 -1
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/agents/apt-executor.md +6 -0
- package/dist/plugin/agents/apt-planner.md +3 -2
- package/dist/plugin/skills/apt-close-task/SKILL.md +2 -2
- package/dist/plugin/skills/apt-debug/SKILL.md +8 -0
- package/dist/plugin/skills/apt-execute/SKILL.md +35 -26
- package/dist/plugin/skills/apt-mockup/SKILL.md +1 -1
- package/dist/plugin/skills/apt-quick/SKILL.md +13 -11
- package/dist/plugin/skills/apt-review/SKILL.md +14 -11
- package/dist/plugin/skills/apt-run/SKILL.md +9 -3
- package/dist/plugin/skills/apt-ship/SKILL.md +27 -5
- package/dist/plugin/skills/apt-watch-ci/SKILL.md +2 -0
- package/dist/schemas/feature-registry.d.ts.map +1 -1
- package/dist/schemas/feature-registry.js +34 -3
- package/dist/schemas/feature-registry.js.map +1 -1
- package/dist/types/config.d.ts +8 -5
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +1 -1
- package/skills/apt-close-task/SKILL.md +2 -2
- package/skills/apt-debug/SKILL.md +8 -0
- package/skills/apt-execute/SKILL.md +35 -26
- package/skills/apt-mockup/SKILL.md +1 -1
- package/skills/apt-quick/SKILL.md +13 -11
- package/skills/apt-review/SKILL.md +14 -11
- package/skills/apt-run/SKILL.md +9 -3
- package/skills/apt-ship/SKILL.md +27 -5
- package/skills/apt-watch-ci/SKILL.md +2 -0
- package/src/cli/artifacts/self-stage.mjs +2 -1
- package/src/cli/cli-wrappers/features-reconcile.mjs +9 -0
- package/src/cli/commands/features-reconcile.mjs +157 -0
- package/src/cli/commands/health-check.mjs +2 -2
- package/src/cli/dispatch.mjs +2 -2
- package/src/cli/features/area-shape.mjs +140 -0
- package/src/cli/features/delta.mjs +625 -0
- package/src/cli/features/reconcile.mjs +1169 -0
- package/src/cli/features/surface-map.mjs +192 -0
- package/src/cli/features/write-root.mjs +140 -0
- package/src/cli/gate/gates/features-reconciled.mjs +70 -0
- package/src/cli/gate/gates/index.mjs +1 -0
- package/src/cli/help.mjs +1 -1
- package/src/cli/release-notes/ship-autodraft.mjs +2 -2
- package/src/cli/variant/gallery.mjs +7 -3
- package/templates/aperant-claude-md-appendix.md +2 -2
- package/workflows/scan-features.md +17 -11
- package/dist/cli/cli-wrappers/features-audit.d.mts +0 -2
- package/dist/cli/cli-wrappers/features-audit.d.mts.map +0 -1
- package/dist/cli/cli-wrappers/features-audit.mjs +0 -8
- package/dist/cli/cli-wrappers/features-audit.mjs.map +0 -1
- package/dist/cli/commands/features-audit.d.mts.map +0 -1
- package/dist/cli/commands/features-audit.mjs +0 -293
- package/dist/cli/commands/features-audit.mjs.map +0 -1
- package/dist/cli/features/registry-audit.d.mts +0 -56
- package/dist/cli/features/registry-audit.d.mts.map +0 -1
- package/dist/cli/features/registry-audit.mjs +0 -264
- package/dist/cli/features/registry-audit.mjs.map +0 -1
- package/src/cli/cli-wrappers/features-audit.mjs +0 -8
- package/src/cli/commands/features-audit.mjs +0 -302
- package/src/cli/features/registry-audit.mjs +0 -254
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* area-shape.mjs — hand-rolled structural validator for feature-registry
|
|
3
|
+
* area files, mirroring `src/schemas/feature-registry.ts` (FeatureRegistrySchema)
|
|
4
|
+
* PLUS the uniqueness refinement (feature ids unique per area; sub_feature ids
|
|
5
|
+
* unique per feature group).
|
|
6
|
+
*
|
|
7
|
+
* WHY A TWIN VALIDATOR EXISTS (ID-07): the bare-node CI check
|
|
8
|
+
* (`bin/features-reconcile-check.mjs`) runs with NO node_modules, so it cannot
|
|
9
|
+
* import zod. This module is node:-builtins only (in fact, zero imports).
|
|
10
|
+
* `feature-registry-schema-parity.test.ts` validates shared fixtures against
|
|
11
|
+
* BOTH this validator and the zod schema so they cannot drift.
|
|
12
|
+
*
|
|
13
|
+
* Semantics mirror zod defaults:
|
|
14
|
+
* - unknown keys are ALLOWED (zod objects are non-strict);
|
|
15
|
+
* - `components` and `sub_features` may be absent (zod `.default([])`);
|
|
16
|
+
* - `route` stays required non-empty — non-routed areas use the literal
|
|
17
|
+
* "(cross-cutting)" (chrome.json precedent), never null.
|
|
18
|
+
*
|
|
19
|
+
* Returns { ok, errors: [] } — never throws.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const ID_RE = /^[a-z0-9-]+$/
|
|
23
|
+
const LAST_SCANNED_RE = /^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]+)?$/
|
|
24
|
+
const TESTABLE_VALUES = new Set(['testable', 'observable', 'blocked'])
|
|
25
|
+
const WIRED_VALUES = new Set([true, false, 'stubbed', 'partial'])
|
|
26
|
+
|
|
27
|
+
function isNonEmptyString(v) {
|
|
28
|
+
return typeof v === 'string' && v.length > 0
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function checkOptionalString(errors, at, v) {
|
|
32
|
+
if (v !== undefined && !isNonEmptyString(v)) {
|
|
33
|
+
errors.push(`${at}: must be a non-empty string when present`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateSubFeature(errors, at, sub) {
|
|
38
|
+
if (!sub || typeof sub !== 'object' || Array.isArray(sub)) {
|
|
39
|
+
errors.push(`${at}: must be an object`)
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
if (!isNonEmptyString(sub.id) || !ID_RE.test(sub.id)) {
|
|
43
|
+
errors.push(`${at}.id: must match ${ID_RE}`)
|
|
44
|
+
}
|
|
45
|
+
if (!isNonEmptyString(sub.name)) errors.push(`${at}.name: required non-empty string`)
|
|
46
|
+
checkOptionalString(errors, `${at}.component`, sub.component)
|
|
47
|
+
if (!isNonEmptyString(sub.type)) errors.push(`${at}.type: required non-empty string`)
|
|
48
|
+
if (!WIRED_VALUES.has(sub.wired)) {
|
|
49
|
+
errors.push(`${at}.wired: must be true|false|"stubbed"|"partial"`)
|
|
50
|
+
}
|
|
51
|
+
if (sub.handler !== undefined && typeof sub.handler !== 'string') {
|
|
52
|
+
errors.push(`${at}.handler: must be a string when present`)
|
|
53
|
+
}
|
|
54
|
+
if (sub.shortcut !== undefined && typeof sub.shortcut !== 'string') {
|
|
55
|
+
errors.push(`${at}.shortcut: must be a string when present`)
|
|
56
|
+
}
|
|
57
|
+
if (sub.testable !== undefined && !TESTABLE_VALUES.has(sub.testable)) {
|
|
58
|
+
errors.push(`${at}.testable: must be "testable"|"observable"|"blocked" when present`)
|
|
59
|
+
}
|
|
60
|
+
if (sub.test_hint !== undefined && typeof sub.test_hint !== 'string') {
|
|
61
|
+
errors.push(`${at}.test_hint: must be a string when present`)
|
|
62
|
+
}
|
|
63
|
+
// default / range / options are `unknown` in the zod schema — anything goes.
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validateFeatureGroup(errors, at, group) {
|
|
67
|
+
if (!group || typeof group !== 'object' || Array.isArray(group)) {
|
|
68
|
+
errors.push(`${at}: must be an object`)
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
if (!isNonEmptyString(group.id) || !ID_RE.test(group.id)) {
|
|
72
|
+
errors.push(`${at}.id: must match ${ID_RE}`)
|
|
73
|
+
}
|
|
74
|
+
if (!isNonEmptyString(group.name)) errors.push(`${at}.name: required non-empty string`)
|
|
75
|
+
checkOptionalString(errors, `${at}.component`, group.component)
|
|
76
|
+
if (group.sub_features !== undefined) {
|
|
77
|
+
if (!Array.isArray(group.sub_features)) {
|
|
78
|
+
errors.push(`${at}.sub_features: must be an array when present`)
|
|
79
|
+
} else {
|
|
80
|
+
const seen = new Set()
|
|
81
|
+
for (const [i, sub] of group.sub_features.entries()) {
|
|
82
|
+
validateSubFeature(errors, `${at}.sub_features[${i}]`, sub)
|
|
83
|
+
const id = sub?.id
|
|
84
|
+
if (isNonEmptyString(id)) {
|
|
85
|
+
if (seen.has(id)) {
|
|
86
|
+
errors.push(`${at}.sub_features[${i}].id: duplicate sub_feature id "${id}" in group`)
|
|
87
|
+
}
|
|
88
|
+
seen.add(id)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Structural + uniqueness validation of an area-file value.
|
|
97
|
+
*
|
|
98
|
+
* @param {unknown} data — parsed JSON of a `.aperant/features/<area>.json`
|
|
99
|
+
* @returns {{ ok: boolean, errors: string[] }}
|
|
100
|
+
*/
|
|
101
|
+
export function validateAreaShape(data) {
|
|
102
|
+
const errors = []
|
|
103
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
104
|
+
return { ok: false, errors: ['area file must be a JSON object'] }
|
|
105
|
+
}
|
|
106
|
+
if (!isNonEmptyString(data.area)) errors.push('area: required non-empty string')
|
|
107
|
+
if (!isNonEmptyString(data.route)) {
|
|
108
|
+
errors.push(
|
|
109
|
+
'route: required non-empty string — use "(cross-cutting)" for non-routed areas, never null',
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
if (data.components !== undefined) {
|
|
113
|
+
if (!Array.isArray(data.components)) {
|
|
114
|
+
errors.push('components: must be an array when present')
|
|
115
|
+
} else {
|
|
116
|
+
for (const [i, c] of data.components.entries()) {
|
|
117
|
+
if (!isNonEmptyString(c)) errors.push(`components[${i}]: must be a non-empty string`)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!isNonEmptyString(data.last_scanned) || !LAST_SCANNED_RE.test(data.last_scanned)) {
|
|
122
|
+
errors.push('last_scanned: must be a YYYY-MM-DD or full ISO 8601 date string')
|
|
123
|
+
}
|
|
124
|
+
if (!Array.isArray(data.features)) {
|
|
125
|
+
errors.push('features: required array')
|
|
126
|
+
} else {
|
|
127
|
+
const seen = new Set()
|
|
128
|
+
for (const [i, group] of data.features.entries()) {
|
|
129
|
+
validateFeatureGroup(errors, `features[${i}]`, group)
|
|
130
|
+
const id = group?.id
|
|
131
|
+
if (isNonEmptyString(id)) {
|
|
132
|
+
if (seen.has(id)) {
|
|
133
|
+
errors.push(`features[${i}].id: duplicate feature id "${id}" in area`)
|
|
134
|
+
}
|
|
135
|
+
seen.add(id)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { ok: errors.length === 0, errors }
|
|
140
|
+
}
|