@aperant/framework 0.6.6 → 0.7.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 +215 -0
- package/dist/cli/commands/check-version.d.mts.map +1 -1
- package/dist/cli/commands/check-version.mjs +76 -1
- package/dist/cli/commands/check-version.mjs.map +1 -1
- package/dist/cli/commands/ci-watch.d.mts.map +1 -1
- package/dist/cli/commands/ci-watch.mjs +73 -5
- package/dist/cli/commands/ci-watch.mjs.map +1 -1
- package/dist/cli/commands/health-check.mjs +1 -1
- package/dist/cli/commands/health-check.mjs.map +1 -1
- package/dist/cli/commands/init.d.mts +17 -1
- package/dist/cli/commands/init.d.mts.map +1 -1
- package/dist/cli/commands/init.mjs +183 -11
- package/dist/cli/commands/init.mjs.map +1 -1
- package/dist/cli/commands/release-notes.d.mts +11 -0
- package/dist/cli/commands/release-notes.d.mts.map +1 -0
- package/dist/cli/commands/release-notes.mjs +173 -0
- package/dist/cli/commands/release-notes.mjs.map +1 -0
- package/dist/cli/commands/route.d.mts.map +1 -1
- package/dist/cli/commands/route.mjs +37 -2
- package/dist/cli/commands/route.mjs.map +1 -1
- package/dist/cli/commands/task.d.mts.map +1 -1
- package/dist/cli/commands/task.mjs +48 -0
- package/dist/cli/commands/task.mjs.map +1 -1
- package/dist/cli/config/load.d.mts +14 -0
- package/dist/cli/config/load.d.mts.map +1 -1
- package/dist/cli/config/load.mjs +40 -0
- package/dist/cli/config/load.mjs.map +1 -1
- package/dist/cli/config/upgrade-gitignore.d.mts.map +1 -1
- package/dist/cli/config/upgrade-gitignore.mjs +6 -0
- package/dist/cli/config/upgrade-gitignore.mjs.map +1 -1
- package/dist/cli/consistency/parse-review.d.mts.map +1 -1
- package/dist/cli/consistency/parse-review.mjs +4 -1
- package/dist/cli/consistency/parse-review.mjs.map +1 -1
- package/dist/cli/dispatch.d.mts.map +1 -1
- package/dist/cli/dispatch.mjs +2 -0
- package/dist/cli/dispatch.mjs.map +1 -1
- package/dist/cli/gate/gates/review-clean.d.mts.map +1 -1
- package/dist/cli/gate/gates/review-clean.mjs +4 -2
- package/dist/cli/gate/gates/review-clean.mjs.map +1 -1
- package/dist/cli/help.d.mts.map +1 -1
- package/dist/cli/help.mjs +1 -0
- package/dist/cli/help.mjs.map +1 -1
- package/dist/cli/host/detect.d.mts +122 -9
- package/dist/cli/host/detect.d.mts.map +1 -1
- package/dist/cli/host/detect.mjs +132 -0
- package/dist/cli/host/detect.mjs.map +1 -1
- package/dist/cli/install/legacy-paths.d.mts +38 -0
- package/dist/cli/install/legacy-paths.d.mts.map +1 -0
- package/dist/cli/install/legacy-paths.mjs +69 -0
- package/dist/cli/install/legacy-paths.mjs.map +1 -0
- package/dist/cli/install/runtime-migrate.d.mts +84 -0
- package/dist/cli/install/runtime-migrate.d.mts.map +1 -0
- package/dist/cli/install/runtime-migrate.mjs +244 -0
- package/dist/cli/install/runtime-migrate.mjs.map +1 -0
- package/dist/cli/install/update-chips.d.mts +23 -0
- package/dist/cli/install/update-chips.d.mts.map +1 -1
- package/dist/cli/install/update-chips.mjs +60 -0
- package/dist/cli/install/update-chips.mjs.map +1 -1
- package/dist/cli/release-notes/compile.d.mts +38 -0
- package/dist/cli/release-notes/compile.d.mts.map +1 -0
- package/dist/cli/release-notes/compile.mjs +244 -0
- package/dist/cli/release-notes/compile.mjs.map +1 -0
- package/dist/cli/release-notes/draft.d.mts +73 -0
- package/dist/cli/release-notes/draft.d.mts.map +1 -0
- package/dist/cli/release-notes/draft.mjs +120 -0
- package/dist/cli/release-notes/draft.mjs.map +1 -0
- package/dist/cli/release-notes/output-dir.d.mts +20 -0
- package/dist/cli/release-notes/output-dir.d.mts.map +1 -0
- package/dist/cli/release-notes/output-dir.mjs +42 -0
- package/dist/cli/release-notes/output-dir.mjs.map +1 -0
- package/dist/cli/release-notes/persona-filter.d.mts +51 -0
- package/dist/cli/release-notes/persona-filter.d.mts.map +1 -0
- package/dist/cli/release-notes/persona-filter.mjs +127 -0
- package/dist/cli/release-notes/persona-filter.mjs.map +1 -0
- package/dist/cli/release-notes/publish.d.mts +23 -0
- package/dist/cli/release-notes/publish.d.mts.map +1 -0
- package/dist/cli/release-notes/publish.mjs +125 -0
- package/dist/cli/release-notes/publish.mjs.map +1 -0
- package/dist/cli/release-notes/ship-autodraft.d.mts +37 -0
- package/dist/cli/release-notes/ship-autodraft.d.mts.map +1 -0
- package/dist/cli/release-notes/ship-autodraft.mjs +97 -0
- package/dist/cli/release-notes/ship-autodraft.mjs.map +1 -0
- package/dist/cli/route/drift-detect.d.mts +20 -0
- package/dist/cli/route/drift-detect.d.mts.map +1 -0
- package/dist/cli/route/drift-detect.mjs +107 -0
- package/dist/cli/route/drift-detect.mjs.map +1 -0
- package/dist/cli/util/aperant-section.d.mts +34 -0
- package/dist/cli/util/aperant-section.d.mts.map +1 -0
- package/dist/cli/util/aperant-section.mjs +127 -0
- package/dist/cli/util/aperant-section.mjs.map +1 -0
- package/dist/cli/util/copy.d.mts +28 -1
- package/dist/cli/util/copy.d.mts.map +1 -1
- package/dist/cli/util/copy.mjs +43 -55
- package/dist/cli/util/copy.mjs.map +1 -1
- package/dist/cli/util/semver.d.mts +17 -0
- package/dist/cli/util/semver.d.mts.map +1 -0
- package/dist/cli/util/semver.mjs +29 -0
- package/dist/cli/util/semver.mjs.map +1 -0
- package/dist/cli/util/skill-installs.d.mts +65 -9
- package/dist/cli/util/skill-installs.d.mts.map +1 -1
- package/dist/cli/util/skill-installs.mjs +130 -21
- package/dist/cli/util/skill-installs.mjs.map +1 -1
- package/dist/cli/util/version-preflight.d.mts +44 -0
- package/dist/cli/util/version-preflight.d.mts.map +1 -0
- package/dist/cli/util/version-preflight.mjs +66 -0
- package/dist/cli/util/version-preflight.mjs.map +1 -0
- package/dist/types/config.d.ts +11 -7
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +133 -133
- package/skills/apt-close-task/SKILL.md +30 -0
- package/skills/apt-diagram/SKILL.md +45 -9
- package/skills/apt-release-notes/SKILL.md +193 -0
- package/skills/apt-release-notes/appendices/persona-voice.md +59 -0
- package/skills/apt-setup/SKILL.md +146 -3
- package/skills/apt-ship/SKILL.md +63 -4
- package/skills/apt-spar/SKILL.md +36 -11
- package/skills/apt-update/SKILL.md +77 -10
- package/skills/apt-watch-ci/SKILL.md +6 -3
- package/src/cli/commands/check-version.mjs +73 -1
- package/src/cli/commands/ci-watch.mjs +74 -5
- package/src/cli/commands/health-check.mjs +1 -1
- package/src/cli/commands/init.mjs +202 -10
- package/src/cli/commands/release-notes.mjs +187 -0
- package/src/cli/commands/route.mjs +38 -2
- package/src/cli/commands/task.mjs +49 -0
- package/src/cli/config/load.mjs +37 -0
- package/src/cli/config/upgrade-gitignore.mjs +6 -0
- package/src/cli/consistency/parse-review.mjs +3 -1
- package/src/cli/dispatch.mjs +2 -0
- package/src/cli/gate/gates/review-clean.mjs +4 -2
- package/src/cli/help.mjs +1 -0
- package/src/cli/host/detect.mjs +135 -0
- package/src/cli/install/legacy-paths.mjs +69 -0
- package/src/cli/install/runtime-migrate.mjs +252 -0
- package/src/cli/install/update-chips.mjs +57 -0
- package/src/cli/release-notes/compile.mjs +261 -0
- package/src/cli/release-notes/draft.mjs +133 -0
- package/src/cli/release-notes/output-dir.mjs +52 -0
- package/src/cli/release-notes/persona-filter.mjs +126 -0
- package/src/cli/release-notes/publish.mjs +128 -0
- package/src/cli/release-notes/ship-autodraft.mjs +106 -0
- package/src/cli/route/drift-detect.mjs +107 -0
- package/src/cli/util/aperant-section.mjs +136 -0
- package/src/cli/util/copy.mjs +43 -56
- package/src/cli/util/semver.mjs +28 -0
- package/src/cli/util/skill-installs.mjs +134 -21
- package/src/cli/util/version-preflight.mjs +65 -0
- package/templates/aperant-claude-md-appendix.md +37 -0
- package/templates/config.json +28 -3
- package/workflows/docs.md +12 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute the autodraft decision.
|
|
3
|
+
*
|
|
4
|
+
* @param {AutodraftInput} input
|
|
5
|
+
* @returns {AutodraftResult}
|
|
6
|
+
*/
|
|
7
|
+
export function decideAutodraft(input: AutodraftInput): AutodraftResult;
|
|
8
|
+
export type FeatureDiffEntry = {
|
|
9
|
+
feature_id: string;
|
|
10
|
+
area?: string | undefined;
|
|
11
|
+
personas?: string[] | undefined;
|
|
12
|
+
};
|
|
13
|
+
export type AutodraftInput = {
|
|
14
|
+
featureDiff: FeatureDiffEntry[];
|
|
15
|
+
personasEnvelope: {
|
|
16
|
+
personas?: Array<{
|
|
17
|
+
ui_json?: unknown;
|
|
18
|
+
}>;
|
|
19
|
+
} | null;
|
|
20
|
+
personaFilter: "primary" | "primary+secondary" | "all";
|
|
21
|
+
requirePrField: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type AutodraftResult = {
|
|
24
|
+
status: "draft";
|
|
25
|
+
line: string;
|
|
26
|
+
persona: string;
|
|
27
|
+
matchedFeatures: string[];
|
|
28
|
+
} | {
|
|
29
|
+
status: "block";
|
|
30
|
+
reason: "no-feature-match" | "no-persona-signal";
|
|
31
|
+
message: string;
|
|
32
|
+
} | {
|
|
33
|
+
status: "warn";
|
|
34
|
+
reason: "no-feature-match" | "no-persona-signal";
|
|
35
|
+
message: string;
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=ship-autodraft.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ship-autodraft.d.mts","sourceRoot":"","sources":["../../../src/cli/release-notes/ship-autodraft.mjs"],"names":[],"mappings":"AAkDA;;;;;GAKG;AACH,uCAHW,cAAc,GACZ,eAAe,CA+B3B;;gBApEa,MAAM;;;;;iBAON,gBAAgB,EAAE;sBAClB;QAAE,QAAQ,CAAC,EAAE,KAAK,CAAC;YAAE,OAAO,CAAC,EAAE,OAAO,CAAA;SAAE,CAAC,CAAA;KAAE,GAAG,IAAI;mBAClD,SAAS,GAAG,mBAAmB,GAAG,KAAK;oBACvC,OAAO;;8BAIR;IACR,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B,GAAG;IACF,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,kBAAkB,GAAG,mBAAmB,CAAC;IACjD,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG;IACF,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,kBAAkB,GAAG,mBAAmB,CAAC;IACjD,OAAO,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* release-notes/ship-autodraft.mjs — apt-ship Section 2.6 helper.
|
|
3
|
+
*
|
|
4
|
+
* Deterministic, unit-testable shape: given a feature-registry diff +
|
|
5
|
+
* personas envelope + persona_filter mode, decide whether the PR body
|
|
6
|
+
* should grow a `Release note: …` line OR ship should block.
|
|
7
|
+
*
|
|
8
|
+
* This module is intentionally pure — the apt-ship SKILL.md body shells
|
|
9
|
+
* `apt-tools features-audit . --diff-since <base> --json` and feeds the
|
|
10
|
+
* envelope here. Keeping the decision pure makes TD-4 trivial: fixture in,
|
|
11
|
+
* decision out, no I/O.
|
|
12
|
+
*/
|
|
13
|
+
import { buildPersonaTierMap, classifyFragment } from './persona-filter.mjs';
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} FeatureDiffEntry
|
|
16
|
+
* @property {string} feature_id
|
|
17
|
+
* @property {string} [area]
|
|
18
|
+
* @property {string[]} [personas]
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* @typedef {Object} AutodraftInput
|
|
22
|
+
* @property {FeatureDiffEntry[]} featureDiff
|
|
23
|
+
* @property {{ personas?: Array<{ ui_json?: unknown }> } | null} personasEnvelope
|
|
24
|
+
* @property {'primary' | 'primary+secondary' | 'all'} personaFilter
|
|
25
|
+
* @property {boolean} requirePrField
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {{
|
|
29
|
+
* status: 'draft',
|
|
30
|
+
* line: string,
|
|
31
|
+
* persona: string,
|
|
32
|
+
* matchedFeatures: string[],
|
|
33
|
+
* } | {
|
|
34
|
+
* status: 'block',
|
|
35
|
+
* reason: 'no-feature-match' | 'no-persona-signal',
|
|
36
|
+
* message: string,
|
|
37
|
+
* } | {
|
|
38
|
+
* status: 'warn',
|
|
39
|
+
* reason: 'no-feature-match' | 'no-persona-signal',
|
|
40
|
+
* message: string,
|
|
41
|
+
* }} AutodraftResult
|
|
42
|
+
*/
|
|
43
|
+
const BLOCK_MESSAGE = '[APT] No release-note signal found — author manual note in PR body or label PR no-notes';
|
|
44
|
+
/**
|
|
45
|
+
* Compute the autodraft decision.
|
|
46
|
+
*
|
|
47
|
+
* @param {AutodraftInput} input
|
|
48
|
+
* @returns {AutodraftResult}
|
|
49
|
+
*/
|
|
50
|
+
export function decideAutodraft(input) {
|
|
51
|
+
const tierMap = buildPersonaTierMap(input.personasEnvelope);
|
|
52
|
+
if (!Array.isArray(input.featureDiff) || input.featureDiff.length === 0) {
|
|
53
|
+
return failClassify(input, 'no-feature-match');
|
|
54
|
+
}
|
|
55
|
+
// Walk the diff; the first feature whose persona resolves into the
|
|
56
|
+
// filter mode wins. (Multi-persona dispatch is intentionally out of
|
|
57
|
+
// scope here — host LLM-side narrative can refine per persona; the
|
|
58
|
+
// CLI just picks a clean signal so ship is decisive.)
|
|
59
|
+
for (const feature of input.featureDiff) {
|
|
60
|
+
if (!Array.isArray(feature.personas) || feature.personas.length === 0)
|
|
61
|
+
continue;
|
|
62
|
+
for (const personaTag of feature.personas) {
|
|
63
|
+
const decision = classifyFragment(personaTag, tierMap, input.personaFilter);
|
|
64
|
+
if (decision.bucket === 'body' && decision.resolvedTier) {
|
|
65
|
+
return {
|
|
66
|
+
status: 'draft',
|
|
67
|
+
line: `Release note: ${feature.feature_id} — ships for ${personaTag}.`,
|
|
68
|
+
persona: personaTag,
|
|
69
|
+
matchedFeatures: [feature.feature_id],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// No persona signal anywhere — features exist but none carry a tag
|
|
75
|
+
// that the roster + filter accept.
|
|
76
|
+
return failClassify(input, 'no-persona-signal');
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Translate a failure reason into the right envelope shape based on
|
|
80
|
+
* `require_pr_field`. require=true → block; require=false → warn (skill
|
|
81
|
+
* proceeds without the line).
|
|
82
|
+
*
|
|
83
|
+
* @param {AutodraftInput} input
|
|
84
|
+
* @param {'no-feature-match' | 'no-persona-signal'} reason
|
|
85
|
+
* @returns {AutodraftResult}
|
|
86
|
+
*/
|
|
87
|
+
function failClassify(input, reason) {
|
|
88
|
+
if (input.requirePrField) {
|
|
89
|
+
return { status: 'block', reason, message: BLOCK_MESSAGE };
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
status: 'warn',
|
|
93
|
+
reason,
|
|
94
|
+
message: `[APT] auto-draft skipped (${reason}) — release_notes.require_pr_field is false; proceeding without a Release note line.`,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=ship-autodraft.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ship-autodraft.mjs","sourceRoot":"","sources":["../../../src/cli/release-notes/ship-autodraft.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAE5E;;;;;GAKG;AAEH;;;;;;GAMG;AAEH;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,aAAa,GAClB,yFAAyF,CAAA;AAE1F;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,KAAK;IACpC,MAAM,OAAO,GAAG,mBAAmB,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAA;IAE3D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzE,OAAO,YAAY,CAAC,KAAK,EAAE,kBAAkB,CAAC,CAAA;IAC/C,CAAC;IAED,mEAAmE;IACnE,oEAAoE;IACpE,mEAAmE;IACnE,sDAAsD;IACtD,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,WAAW,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE,SAAQ;QAC/E,KAAK,MAAM,UAAU,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YAC3C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,CAAC,aAAa,CAAC,CAAA;YAC3E,IAAI,QAAQ,CAAC,MAAM,KAAK,MAAM,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;gBACzD,OAAO;oBACN,MAAM,EAAE,OAAO;oBACf,IAAI,EAAE,iBAAiB,OAAO,CAAC,UAAU,gBAAgB,UAAU,GAAG;oBACtE,OAAO,EAAE,UAAU;oBACnB,eAAe,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC;iBACrC,CAAA;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAED,mEAAmE;IACnE,mCAAmC;IACnC,OAAO,YAAY,CAAC,KAAK,EAAE,mBAAmB,CAAC,CAAA;AAChD,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,YAAY,CAAC,KAAK,EAAE,MAAM;IAClC,IAAI,KAAK,CAAC,cAAc,EAAE,CAAC;QAC1B,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;IAC3D,CAAC;IACD,OAAO;QACN,MAAM,EAAE,MAAM;QACd,MAAM;QACN,OAAO,EAAE,6BAA6B,MAAM,sFAAsF;KAClI,CAAA;AACF,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect whether the workspace source for `@aperant/framework` is ahead
|
|
3
|
+
* of the installed kernel.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} targetDir Absolute path the route was invoked against.
|
|
6
|
+
* @param {string|null} installedVersion The installed kernel's version (semver string), or null when unknown.
|
|
7
|
+
* @returns {null | {
|
|
8
|
+
* source_path: string,
|
|
9
|
+
* source_version: string,
|
|
10
|
+
* installed_version: string,
|
|
11
|
+
* drift: true,
|
|
12
|
+
* }}
|
|
13
|
+
*/
|
|
14
|
+
export function detectSourceWorkspaceDrift(targetDir: string, installedVersion: string | null): null | {
|
|
15
|
+
source_path: string;
|
|
16
|
+
source_version: string;
|
|
17
|
+
installed_version: string;
|
|
18
|
+
drift: true;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=drift-detect.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detect.d.mts","sourceRoot":"","sources":["../../../src/cli/route/drift-detect.mjs"],"names":[],"mappings":"AAwDA;;;;;;;;;;;;GAYG;AACH,sDATW,MAAM,oBACN,MAAM,GAAC,IAAI,GACT,IAAI,GAAG;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE,IAAI,CAAC;CACb,CAuCH"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* route/drift-detect.mjs — FRAMEWORK-BUG-017 (rung 1 of 3).
|
|
3
|
+
*
|
|
4
|
+
* In-band detection of the "installed kernel drifts behind source
|
|
5
|
+
* workspace" failure mode. When an Aperant contributor self-hosts the
|
|
6
|
+
* framework out of a monorepo checkout, the kernel at
|
|
7
|
+
* `.aperant/deps/node_modules/@aperant/framework/` lags behind the
|
|
8
|
+
* source at `packages/framework/` after every same-PR framework fix.
|
|
9
|
+
* The SessionStart worker only knows about npm — it cannot tell that
|
|
10
|
+
* the workspace is ahead of the install.
|
|
11
|
+
*
|
|
12
|
+
* This module walks up from `targetDir`, looking for a sibling
|
|
13
|
+
* `packages/framework/package.json`. When found AND its `version`
|
|
14
|
+
* differs from the installed kernel's `version`, it returns a synthetic
|
|
15
|
+
* `update_check`-shaped object so `route.mjs` can surface the chip
|
|
16
|
+
* immediately — without waiting for the SessionStart hook to fire.
|
|
17
|
+
*
|
|
18
|
+
* Safe for the route hot path: synchronous, file-existence checks
|
|
19
|
+
* only, bounded walk (6 segments max), no network, no spawn.
|
|
20
|
+
*
|
|
21
|
+
* Security (Codex review edit 2): the workspace `package.json:version`
|
|
22
|
+
* is validated against the same semver-shape regex `update-chips.mjs`
|
|
23
|
+
* uses on the cache. A poisoned workspace `package.json` cannot
|
|
24
|
+
* propagate a non-semver `latest_version` into the synthesized
|
|
25
|
+
* `update_check` block.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
28
|
+
import { dirname, join } from 'node:path';
|
|
29
|
+
import { compareVersions, SEMVER_RE } from '../util/semver.mjs';
|
|
30
|
+
/** Max directory frames to walk before giving up. */
|
|
31
|
+
const MAX_WALK_DEPTH = 6;
|
|
32
|
+
/**
|
|
33
|
+
* Walk up from `start`, returning every directory frame including
|
|
34
|
+
* `start` itself. Stops at the filesystem root or at a depth of
|
|
35
|
+
* `MAX_WALK_DEPTH` (whichever comes first). The hard cap keeps this
|
|
36
|
+
* cheap on the route hot path and contains accidental scans on deeply
|
|
37
|
+
* nested input paths.
|
|
38
|
+
*
|
|
39
|
+
* @param {string} start
|
|
40
|
+
* @returns {string[]}
|
|
41
|
+
*/
|
|
42
|
+
function walkUp(start) {
|
|
43
|
+
const frames = [];
|
|
44
|
+
let cur = start;
|
|
45
|
+
for (let i = 0; i < MAX_WALK_DEPTH; i++) {
|
|
46
|
+
frames.push(cur);
|
|
47
|
+
const parent = dirname(cur);
|
|
48
|
+
if (parent === cur)
|
|
49
|
+
break;
|
|
50
|
+
cur = parent;
|
|
51
|
+
}
|
|
52
|
+
return frames;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Detect whether the workspace source for `@aperant/framework` is ahead
|
|
56
|
+
* of the installed kernel.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} targetDir Absolute path the route was invoked against.
|
|
59
|
+
* @param {string|null} installedVersion The installed kernel's version (semver string), or null when unknown.
|
|
60
|
+
* @returns {null | {
|
|
61
|
+
* source_path: string,
|
|
62
|
+
* source_version: string,
|
|
63
|
+
* installed_version: string,
|
|
64
|
+
* drift: true,
|
|
65
|
+
* }}
|
|
66
|
+
*/
|
|
67
|
+
export function detectSourceWorkspaceDrift(targetDir, installedVersion) {
|
|
68
|
+
if (!targetDir || typeof targetDir !== 'string')
|
|
69
|
+
return null;
|
|
70
|
+
if (!installedVersion || typeof installedVersion !== 'string')
|
|
71
|
+
return null;
|
|
72
|
+
if (!SEMVER_RE.test(installedVersion))
|
|
73
|
+
return null;
|
|
74
|
+
for (const dir of walkUp(targetDir)) {
|
|
75
|
+
const candidate = join(dir, 'packages', 'framework', 'package.json');
|
|
76
|
+
if (!existsSync(candidate))
|
|
77
|
+
continue;
|
|
78
|
+
let pkg;
|
|
79
|
+
try {
|
|
80
|
+
pkg = JSON.parse(readFileSync(candidate, 'utf-8'));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
// Validate the workspace package is actually @aperant/framework —
|
|
86
|
+
// guard against false matches on unrelated monorepos that happen
|
|
87
|
+
// to have a `packages/framework/` directory.
|
|
88
|
+
if (pkg?.name !== '@aperant/framework')
|
|
89
|
+
continue;
|
|
90
|
+
const sourceVersion = pkg?.version;
|
|
91
|
+
if (typeof sourceVersion !== 'string' || !SEMVER_RE.test(sourceVersion))
|
|
92
|
+
return null;
|
|
93
|
+
// Only report drift when workspace source is strictly ahead of the installed
|
|
94
|
+
// kernel. Equal (already up-to-date) and behind (installed is newer) both
|
|
95
|
+
// return null — only source-ahead is a drift worth surfacing.
|
|
96
|
+
if (compareVersions(sourceVersion, installedVersion) <= 0)
|
|
97
|
+
return null;
|
|
98
|
+
return {
|
|
99
|
+
source_path: candidate,
|
|
100
|
+
source_version: sourceVersion,
|
|
101
|
+
installed_version: installedVersion,
|
|
102
|
+
drift: true,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=drift-detect.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drift-detect.mjs","sourceRoot":"","sources":["../../../src/cli/route/drift-detect.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE/D,qDAAqD;AACrD,MAAM,cAAc,GAAG,CAAC,CAAA;AAExB;;;;;;;;;GASG;AACH,SAAS,MAAM,CAAC,KAAK;IACpB,MAAM,MAAM,GAAG,EAAE,CAAA;IACjB,IAAI,GAAG,GAAG,KAAK,CAAA;IACf,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,cAAc,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QAChB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC3B,IAAI,MAAM,KAAK,GAAG;YAAE,MAAK;QACzB,GAAG,GAAG,MAAM,CAAA;IACb,CAAC;IACD,OAAO,MAAM,CAAA;AACd,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,0BAA0B,CAAC,SAAS,EAAE,gBAAgB;IACrE,IAAI,CAAC,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC5D,IAAI,CAAC,gBAAgB,IAAI,OAAO,gBAAgB,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC1E,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,gBAAgB,CAAC;QAAE,OAAO,IAAI,CAAA;IAElD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QACrC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,CAAC,CAAA;QACpE,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,SAAQ;QAEpC,IAAI,GAAG,CAAA;QACP,IAAI,CAAC;YACJ,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC,CAAA;QACnD,CAAC;QAAC,MAAM,CAAC;YACR,OAAO,IAAI,CAAA;QACZ,CAAC;QAED,kEAAkE;QAClE,iEAAiE;QACjE,6CAA6C;QAC7C,IAAI,GAAG,EAAE,IAAI,KAAK,oBAAoB;YAAE,SAAQ;QAEhD,MAAM,aAAa,GAAG,GAAG,EAAE,OAAO,CAAA;QAClC,IAAI,OAAO,aAAa,KAAK,QAAQ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC;YAAE,OAAO,IAAI,CAAA;QACpF,6EAA6E;QAC7E,0EAA0E;QAC1E,8DAA8D;QAC9D,IAAI,eAAe,CAAC,aAAa,EAAE,gBAAgB,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAA;QAEtE,OAAO;YACN,WAAW,EAAE,SAAS;YACtB,cAAc,EAAE,aAAa;YAC7B,iBAAiB,EAAE,gBAAgB;YACnC,KAAK,EAAE,IAAI;SACX,CAAA;IACF,CAAC;IAED,OAAO,IAAI,CAAA;AACZ,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build the full "## Aperant Framework" section, wrapped in the
|
|
3
|
+
* `<!-- APT:framework-* -->` markers.
|
|
4
|
+
*
|
|
5
|
+
* @param {Object} opts
|
|
6
|
+
* @param {string} opts.canonicalRoot Absolute path to the framework root
|
|
7
|
+
* (the directory that contains
|
|
8
|
+
* `templates/aperant-claude-md-appendix.md`).
|
|
9
|
+
* @param {string} opts.targetDir Project root (passed to discoverSkills).
|
|
10
|
+
* @param {string} [opts.cli] Host CLI label (forwarded to
|
|
11
|
+
* getTrustedSkillSources so the install
|
|
12
|
+
* root for the runtime is included).
|
|
13
|
+
* @returns {string} The full section, including markers
|
|
14
|
+
* and trailing newline before the
|
|
15
|
+
* end-marker. No leading newline.
|
|
16
|
+
*/
|
|
17
|
+
export function buildAptSection({ canonicalRoot, targetDir, cli }: {
|
|
18
|
+
canonicalRoot: string;
|
|
19
|
+
targetDir: string;
|
|
20
|
+
cli?: string | undefined;
|
|
21
|
+
}): string;
|
|
22
|
+
/**
|
|
23
|
+
* Extract the `/apt:<slug>` (or bare `/apt`) row IDs from the
|
|
24
|
+
* `<!-- APT:framework-* -->` section of an existing instruction file.
|
|
25
|
+
* Returns `[]` when the file has no APT section.
|
|
26
|
+
*
|
|
27
|
+
* Used by `injectInstructionFile` to detect foreign rows that would be
|
|
28
|
+
* silently clobbered by a regeneration — see `--force-overwrite-catalog`.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} text Full instruction-file contents.
|
|
31
|
+
* @returns {string[]} Row IDs in file order, e.g. `['/apt', '/apt:quick', …]`.
|
|
32
|
+
*/
|
|
33
|
+
export function extractAptSectionRowIds(text: string): string[];
|
|
34
|
+
//# sourceMappingURL=aperant-section.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aperant-section.d.mts","sourceRoot":"","sources":["../../../src/cli/util/aperant-section.mjs"],"names":[],"mappings":"AAuDA;;;;;;;;;;;;;;;GAeG;AACH,mEAXG;IAAqB,aAAa,EAA1B,MAAM;IAGO,SAAS,EAAtB,MAAM;IACQ,GAAG;CAGzB,GAAU,MAAM,CAqClB;AAED;;;;;;;;;;GAUG;AACH,8CAHW,MAAM,GACJ,MAAM,EAAE,CAoBpB"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* util/aperant-section.mjs — generator for the CLAUDE.md / AGENTS.md
|
|
3
|
+
* "## Aperant Framework" section (FRAMEWORK-BUG-023).
|
|
4
|
+
*
|
|
5
|
+
* Replaces the previously-hardcoded `APT_SECTION` constant in copy.mjs.
|
|
6
|
+
* The skill TABLE is rebuilt from `discoverSkills` (filesystem source of
|
|
7
|
+
* truth — same helper the router uses at runtime) so install and runtime
|
|
8
|
+
* can never disagree about the catalog.
|
|
9
|
+
*
|
|
10
|
+
* Curated-doc risk: the appendix template
|
|
11
|
+
* (`templates/aperant-claude-md-appendix.md`) still contains hand-written
|
|
12
|
+
* sub-sections — "Fast Path Guarantee", "Discoverability —
|
|
13
|
+
* `apt-tools modes list`", "Feature Registry", "Config". These are small
|
|
14
|
+
* (~30 lines), rarely change, and rot would be visible (sections about
|
|
15
|
+
* non-existent features). Acceptable tradeoff per plan §Approach (c).
|
|
16
|
+
*/
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
|
+
import { getTrustedSkillSources } from '../config/load.mjs';
|
|
20
|
+
import { discoverSkills } from '../route/skill-discover.mjs';
|
|
21
|
+
const SECTION_START = '<!-- APT:framework-start -->';
|
|
22
|
+
const SECTION_END = '<!-- APT:framework-end -->';
|
|
23
|
+
const SECTION_HEADING = '## Aperant Framework';
|
|
24
|
+
const APPENDIX_REL_PATH = ['templates', 'aperant-claude-md-appendix.md'];
|
|
25
|
+
/**
|
|
26
|
+
* Escape a SKILL `description` for safe inclusion as a markdown table cell.
|
|
27
|
+
* The only character we have to defend against is the pipe (`|`) — it
|
|
28
|
+
* terminates the table column. `apt:triage` ships with a literal `|` in
|
|
29
|
+
* its description, so this is a live case, not a theoretical one.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} text
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
function escapePipes(text) {
|
|
35
|
+
return text.replace(/\|/g, '\\|');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Deterministic sort: the bare `apt` router skill first, then ASCII-sorted
|
|
39
|
+
* by `name`. The router goes first so the generated table mirrors the user's
|
|
40
|
+
* mental model — the entry point, then everything reachable from it.
|
|
41
|
+
*
|
|
42
|
+
* @param {{ name: string }} a
|
|
43
|
+
* @param {{ name: string }} b
|
|
44
|
+
* @returns {number}
|
|
45
|
+
*/
|
|
46
|
+
function sortSkills(a, b) {
|
|
47
|
+
if (a.name === 'apt' && b.name !== 'apt')
|
|
48
|
+
return -1;
|
|
49
|
+
if (b.name === 'apt' && a.name !== 'apt')
|
|
50
|
+
return 1;
|
|
51
|
+
return a.name.localeCompare(b.name);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build the full "## Aperant Framework" section, wrapped in the
|
|
55
|
+
* `<!-- APT:framework-* -->` markers.
|
|
56
|
+
*
|
|
57
|
+
* @param {Object} opts
|
|
58
|
+
* @param {string} opts.canonicalRoot Absolute path to the framework root
|
|
59
|
+
* (the directory that contains
|
|
60
|
+
* `templates/aperant-claude-md-appendix.md`).
|
|
61
|
+
* @param {string} opts.targetDir Project root (passed to discoverSkills).
|
|
62
|
+
* @param {string} [opts.cli] Host CLI label (forwarded to
|
|
63
|
+
* getTrustedSkillSources so the install
|
|
64
|
+
* root for the runtime is included).
|
|
65
|
+
* @returns {string} The full section, including markers
|
|
66
|
+
* and trailing newline before the
|
|
67
|
+
* end-marker. No leading newline.
|
|
68
|
+
*/
|
|
69
|
+
export function buildAptSection({ canonicalRoot, targetDir, cli }) {
|
|
70
|
+
const trustedSkillSources = getTrustedSkillSources(targetDir, cli);
|
|
71
|
+
const discovered = discoverSkills({ targetDir, trustedSkillSources });
|
|
72
|
+
const skills = discovered.skills
|
|
73
|
+
.filter((s) => s.user_invocable === true && s.internal === false)
|
|
74
|
+
.slice()
|
|
75
|
+
.sort(sortSkills);
|
|
76
|
+
const rows = skills.map((s) => `| \`/${s.name}\` | ${escapePipes(s.description)} |`);
|
|
77
|
+
const tableBlock = [
|
|
78
|
+
'### Available Skills',
|
|
79
|
+
'| Skill | Description |',
|
|
80
|
+
'|-------|-------------|',
|
|
81
|
+
...rows,
|
|
82
|
+
].join('\n');
|
|
83
|
+
const appendixPath = join(canonicalRoot, ...APPENDIX_REL_PATH);
|
|
84
|
+
let appendix;
|
|
85
|
+
try {
|
|
86
|
+
appendix = readFileSync(appendixPath, 'utf-8').replace(/\n+$/, '');
|
|
87
|
+
}
|
|
88
|
+
catch (cause) {
|
|
89
|
+
const err = new Error(`aperant-section: template not found at ${appendixPath} — re-install the framework package`, { cause });
|
|
90
|
+
err.code = 'aperant-section.template-missing';
|
|
91
|
+
throw err;
|
|
92
|
+
}
|
|
93
|
+
const body = [SECTION_HEADING, '', appendix, '', tableBlock].join('\n');
|
|
94
|
+
return `${SECTION_START}\n${body}\n${SECTION_END}`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Extract the `/apt:<slug>` (or bare `/apt`) row IDs from the
|
|
98
|
+
* `<!-- APT:framework-* -->` section of an existing instruction file.
|
|
99
|
+
* Returns `[]` when the file has no APT section.
|
|
100
|
+
*
|
|
101
|
+
* Used by `injectInstructionFile` to detect foreign rows that would be
|
|
102
|
+
* silently clobbered by a regeneration — see `--force-overwrite-catalog`.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} text Full instruction-file contents.
|
|
105
|
+
* @returns {string[]} Row IDs in file order, e.g. `['/apt', '/apt:quick', …]`.
|
|
106
|
+
*/
|
|
107
|
+
export function extractAptSectionRowIds(text) {
|
|
108
|
+
const match = text.match(/<!-- APT:framework-start -->([\s\S]*?)<!-- APT:framework-end -->/);
|
|
109
|
+
if (!match)
|
|
110
|
+
return [];
|
|
111
|
+
// Strip HTML comments (including multiline) before scanning for row IDs so
|
|
112
|
+
// that commented-out example rows don't trigger false-positive foreign-row exits.
|
|
113
|
+
const section = match[1].replace(/<!--[\s\S]*?-->/g, '');
|
|
114
|
+
const ids = [];
|
|
115
|
+
// Match table rows whose first cell is `/apt` or `/apt:<slug>` wrapped in
|
|
116
|
+
// backticks. The leading `|` plus optional whitespace anchors us to a
|
|
117
|
+
// markdown table cell; the trailing backtick + ` |` anchors the column
|
|
118
|
+
// boundary so we don't match prose mentions like "the `/apt` router".
|
|
119
|
+
const rowRe = /^\|\s*`(\/apt(?::[a-z0-9-]+)?)`\s*\|/gm;
|
|
120
|
+
let m = rowRe.exec(section);
|
|
121
|
+
while (m !== null) {
|
|
122
|
+
ids.push(m[1]);
|
|
123
|
+
m = rowRe.exec(section);
|
|
124
|
+
}
|
|
125
|
+
return ids;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=aperant-section.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aperant-section.mjs","sourceRoot":"","sources":["../../../src/cli/util/aperant-section.mjs"],"names":[],"mappings":"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"}
|
package/dist/cli/util/copy.d.mts
CHANGED
|
@@ -1,3 +1,30 @@
|
|
|
1
1
|
export function copyDirRecursive(src: any, dest: any): void;
|
|
2
|
-
|
|
2
|
+
/**
|
|
3
|
+
* Write the (already-generated) APT section into `filePath`. Creates the
|
|
4
|
+
* file if missing; replaces the section in-place if the markers are
|
|
5
|
+
* present.
|
|
6
|
+
*
|
|
7
|
+
* FRAMEWORK-BUG-023 — before overwriting an existing section, extract the
|
|
8
|
+
* row IDs in the on-disk file and compare to the row IDs in the freshly
|
|
9
|
+
* generated section. If the file contains rows that are NOT in the
|
|
10
|
+
* generated set ("foreign rows"), the regen would silently clobber them.
|
|
11
|
+
* In that case we abort and let the caller surface a structured error.
|
|
12
|
+
* The `--force-overwrite-catalog` escape hatch flips `force: true`.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} filePath
|
|
15
|
+
* @param {Object} opts
|
|
16
|
+
* @param {string} opts.aptSection Output of buildAptSection() — wrapped
|
|
17
|
+
* in `<!-- APT:framework-* -->` markers.
|
|
18
|
+
* @param {boolean} [opts.force=false]
|
|
19
|
+
* Bypass the foreign-row check.
|
|
20
|
+
* @returns {{ status: 'created' | 'updated' | 'aborted-foreign-rows', written: boolean, foreignRowIds?: string[] }}
|
|
21
|
+
*/
|
|
22
|
+
export function injectInstructionFile(filePath: string, { aptSection, force }: {
|
|
23
|
+
aptSection: string;
|
|
24
|
+
force?: boolean | undefined;
|
|
25
|
+
}): {
|
|
26
|
+
status: "created" | "updated" | "aborted-foreign-rows";
|
|
27
|
+
written: boolean;
|
|
28
|
+
foreignRowIds?: string[];
|
|
29
|
+
};
|
|
3
30
|
//# sourceMappingURL=copy.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy.d.mts","sourceRoot":"","sources":["../../../src/cli/util/copy.mjs"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"copy.d.mts","sourceRoot":"","sources":["../../../src/cli/util/copy.mjs"],"names":[],"mappings":"AAwBA,4DAWC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,gDARW,MAAM,yBAEd;IAAqB,UAAU,EAAvB,MAAM;IAES,KAAK;CAE5B,GAAU;IAAE,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,sBAAsB,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA0BlH"}
|
package/dist/cli/util/copy.mjs
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* copy.mjs — filesystem copy + instruction-file injection helpers (C29).
|
|
3
3
|
*
|
|
4
|
-
* `copyDirRecursive` is a pure leaf
|
|
5
|
-
* CLAUDE.md / AGENTS.md "<!-- APT:framework-* -->" section
|
|
4
|
+
* `copyDirRecursive` is a pure leaf. `injectInstructionFile` owns the
|
|
5
|
+
* CLAUDE.md / AGENTS.md "<!-- APT:framework-* -->" section write path.
|
|
6
|
+
*
|
|
7
|
+
* The APT section TEXT is no longer assembled here — it is generated by
|
|
8
|
+
* `util/aperant-section.mjs#buildAptSection` and passed in (FRAMEWORK-BUG-023).
|
|
9
|
+
* Callers compute the section once (from the filesystem skill catalog) and
|
|
10
|
+
* hand it to this helper. The defensive foreign-row check stays in this
|
|
11
|
+
* file so the read/compare/write happens atomically against `filePath`.
|
|
6
12
|
*/
|
|
7
13
|
import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'node:fs';
|
|
8
14
|
import { join } from 'node:path';
|
|
15
|
+
import { extractAptSectionRowIds } from './aperant-section.mjs';
|
|
9
16
|
export function copyDirRecursive(src, dest) {
|
|
10
17
|
mkdirSync(dest, { recursive: true });
|
|
11
18
|
for (const entry of readdirSync(src, { withFileTypes: true })) {
|
|
@@ -19,63 +26,44 @@ export function copyDirRecursive(src, dest) {
|
|
|
19
26
|
}
|
|
20
27
|
}
|
|
21
28
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
| \`/apt:stress-test\` | Scenario simulation to catch gaps in plans |
|
|
44
|
-
| \`/apt:ship\` | Create PR with QA scores and decision traceability |
|
|
45
|
-
| \`/apt:pause\` | Human-initiated session handoff — save state when YOU need to stop (not for agent-initiated bailouts) |
|
|
46
|
-
| \`/apt:resume\` | Restore context from a paused session and route to next action |
|
|
47
|
-
| \`/apt:docs\` | Generate/update documentation verified against the actual codebase |
|
|
48
|
-
| \`/apt:setup\` | Change framework preferences (verification style, multi-model, branching) |
|
|
49
|
-
|
|
50
|
-
### Feature Registry
|
|
51
|
-
The feature registry (\`.aperant/features/\`) is a living inventory of every interactive
|
|
52
|
-
feature in the app. It's the source of truth for what to verify and review.
|
|
53
|
-
|
|
54
|
-
- **Created by:** \`/apt:scan\` (initial scan or full refresh)
|
|
55
|
-
- **Updated by:** \`/apt:execute\` post-step (\`apt-tools features-audit --apply-stubs\` auto-registers new UI/core/hook/IPC files per the docs-coherence policy)
|
|
56
|
-
- **Consumed by:** \`/apt:verify-proof\` (generates test cases from registry)
|
|
57
|
-
- **Checked by:** \`/apt:review\` Pass 6 (flags unregistered new components, applies stubs for auto-update policies)
|
|
58
|
-
|
|
59
|
-
Run \`/apt:scan\` after setting up Aperant on a brownfield project, or after major changes.
|
|
60
|
-
|
|
61
|
-
### Config
|
|
62
|
-
- Settings: \`.aperant/config.json\` (run \`/apt:setup\` to change)
|
|
63
|
-
- Features: \`.aperant/features/\` (feature registry -- run \`/apt:scan\` to create)
|
|
64
|
-
- State: \`.aperant/state.json\` (execution state)
|
|
65
|
-
- Evidence: \`.aperant/proof/\` (proof reports and screenshots)
|
|
66
|
-
- Prompts: \`.aperant/prompts/\` (agent prompt templates)
|
|
67
|
-
<!-- APT:framework-end -->`;
|
|
68
|
-
export function injectInstructionFile(filePath) {
|
|
29
|
+
/**
|
|
30
|
+
* Write the (already-generated) APT section into `filePath`. Creates the
|
|
31
|
+
* file if missing; replaces the section in-place if the markers are
|
|
32
|
+
* present.
|
|
33
|
+
*
|
|
34
|
+
* FRAMEWORK-BUG-023 — before overwriting an existing section, extract the
|
|
35
|
+
* row IDs in the on-disk file and compare to the row IDs in the freshly
|
|
36
|
+
* generated section. If the file contains rows that are NOT in the
|
|
37
|
+
* generated set ("foreign rows"), the regen would silently clobber them.
|
|
38
|
+
* In that case we abort and let the caller surface a structured error.
|
|
39
|
+
* The `--force-overwrite-catalog` escape hatch flips `force: true`.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} filePath
|
|
42
|
+
* @param {Object} opts
|
|
43
|
+
* @param {string} opts.aptSection Output of buildAptSection() — wrapped
|
|
44
|
+
* in `<!-- APT:framework-* -->` markers.
|
|
45
|
+
* @param {boolean} [opts.force=false]
|
|
46
|
+
* Bypass the foreign-row check.
|
|
47
|
+
* @returns {{ status: 'created' | 'updated' | 'aborted-foreign-rows', written: boolean, foreignRowIds?: string[] }}
|
|
48
|
+
*/
|
|
49
|
+
export function injectInstructionFile(filePath, { aptSection, force = false }) {
|
|
69
50
|
const existing = existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';
|
|
70
51
|
if (existing.includes('<!-- APT:framework-start -->')) {
|
|
71
|
-
|
|
72
|
-
|
|
52
|
+
if (!force) {
|
|
53
|
+
const existingIds = new Set(extractAptSectionRowIds(existing));
|
|
54
|
+
const generatedIds = new Set(extractAptSectionRowIds(aptSection));
|
|
55
|
+
const foreignRowIds = [...existingIds].filter((id) => !generatedIds.has(id));
|
|
56
|
+
if (foreignRowIds.length > 0) {
|
|
57
|
+
return { status: 'aborted-foreign-rows', written: false, foreignRowIds };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const updated = existing.replace(/<!-- APT:framework-start -->[\s\S]*?<!-- APT:framework-end -->/, aptSection);
|
|
73
61
|
writeFileSync(filePath, updated, 'utf-8');
|
|
74
|
-
return 'updated';
|
|
62
|
+
return { status: 'updated', written: true };
|
|
75
63
|
}
|
|
76
|
-
// Append
|
|
64
|
+
// Append (greenfield create — no foreign-row concern).
|
|
77
65
|
const separator = existing.length > 0 ? '\n\n' : '';
|
|
78
|
-
writeFileSync(filePath, existing + separator +
|
|
79
|
-
return 'created';
|
|
66
|
+
writeFileSync(filePath, existing + separator + aptSection + '\n', 'utf-8');
|
|
67
|
+
return { status: 'created', written: true };
|
|
80
68
|
}
|
|
81
69
|
//# sourceMappingURL=copy.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"copy.mjs","sourceRoot":"","sources":["../../../src/cli/util/copy.mjs"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"copy.mjs","sourceRoot":"","sources":["../../../src/cli/util/copy.mjs"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACN,YAAY,EACZ,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,aAAa,GACb,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAA;AAE/D,MAAM,UAAU,gBAAgB,CAAC,GAAG,EAAE,IAAI;IACzC,SAAS,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACpC,KAAK,MAAM,KAAK,IAAI,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;QACvC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACzB,gBAAgB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QACpC,CAAC;aAAM,CAAC;YACP,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QAChC,CAAC;IACF,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAQ,EAAE,EAAE,UAAU,EAAE,KAAK,GAAG,KAAK,EAAE;IAC5E,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAE5E,IAAI,QAAQ,CAAC,QAAQ,CAAC,8BAA8B,CAAC,EAAE,CAAC;QACvD,IAAI,CAAC,KAAK,EAAE,CAAC;YACZ,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,uBAAuB,CAAC,QAAQ,CAAC,CAAC,CAAA;YAC9D,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,uBAAuB,CAAC,UAAU,CAAC,CAAC,CAAA;YACjE,MAAM,aAAa,GAAG,CAAC,GAAG,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAA;YAC5E,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,OAAO,EAAE,MAAM,EAAE,sBAAsB,EAAE,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,CAAA;YACzE,CAAC;QACF,CAAC;QACD,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAC/B,gEAAgE,EAChE,UAAU,CACV,CAAA;QACD,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,CAAC,CAAA;QACzC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;IAC5C,CAAC;IAED,uDAAuD;IACvD,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAA;IACnD,aAAa,CAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,GAAG,UAAU,GAAG,IAAI,EAAE,OAAO,CAAC,CAAA;IAC1E,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAC5C,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare two semver-shape strings. Returns -1 / 0 / 1. Ignores
|
|
3
|
+
* pre-release + build metadata (sufficient for "min version" checks
|
|
4
|
+
* where we only care about the MAJOR.MINOR.PATCH triple).
|
|
5
|
+
*
|
|
6
|
+
* @param {string} a
|
|
7
|
+
* @param {string} b
|
|
8
|
+
* @returns {-1 | 0 | 1}
|
|
9
|
+
*/
|
|
10
|
+
export function compareVersions(a: string, b: string): -1 | 0 | 1;
|
|
11
|
+
/**
|
|
12
|
+
* util/semver.mjs — shared semver helpers used by route/drift-detect.mjs
|
|
13
|
+
* and util/version-preflight.mjs. Extracted to avoid duplication (QUA-002).
|
|
14
|
+
*/
|
|
15
|
+
/** Semver-shape pattern — mirrors install/update-chips.mjs:64. */
|
|
16
|
+
export const SEMVER_RE: RegExp;
|
|
17
|
+
//# sourceMappingURL=semver.d.mts.map
|