pi-aia-asf 0.17.1 → 0.17.3
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 +23 -3
- package/lib/code-health/tools.mjs +65 -12
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,13 +7,33 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.17.
|
|
10
|
+
## [0.17.3] - 2026-11-07
|
|
11
11
|
|
|
12
12
|
### Added
|
|
13
13
|
|
|
14
|
-
- (describe changes for 0.17.
|
|
14
|
+
- (describe changes for 0.17.3)
|
|
15
|
+
|
|
16
|
+
## [0.17.2] - 2026-11-07
|
|
17
|
+
|
|
18
|
+
### Fixed — the tools were installed but never found
|
|
15
19
|
|
|
16
|
-
|
|
20
|
+
0.17.1 declared the toolchain as dependencies and then looked for it in
|
|
21
|
+
`<pi-aia-asf>/node_modules`, which does not exist in the layout pi installs: npm
|
|
22
|
+
**hoists** a package's dependencies into the shared `node_modules` one level up.
|
|
23
|
+
Every tool therefore read as missing, the gate fell back to a system `eslint` (v8,
|
|
24
|
+
which rejects `--no-config-lookup`), and eight metrics came back as errors — the
|
|
25
|
+
same blindness as before, with a different message.
|
|
26
|
+
|
|
27
|
+
- Resolution now walks the real `node_modules` chain from ASF's package directory
|
|
28
|
+
upward, exactly as node resolves, so a hoisted install is found. `require.resolve`
|
|
29
|
+
is only a last resort: on Debian it escaped the install entirely and returned
|
|
30
|
+
`/usr/share/nodejs/eslint`.
|
|
31
|
+
- Packages with no `main` field (jscpd) can never be found by `require.resolve`;
|
|
32
|
+
the directory walk finds them.
|
|
33
|
+
- A regression test builds the hoisted layout on disk and asserts all five tools
|
|
34
|
+
resolve inside the install prefix, with ASF's copy winning over PATH.
|
|
35
|
+
|
|
36
|
+
## [0.17.1] - 2026-11-07
|
|
17
37
|
|
|
18
38
|
### Added — the health gate ships its own toolchain
|
|
19
39
|
|
|
@@ -14,9 +14,10 @@
|
|
|
14
14
|
* 3. PATH — a global install
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import { existsSync, statSync } from "node:fs";
|
|
17
|
+
import { existsSync, statSync, readFileSync } from "node:fs";
|
|
18
18
|
import { join, dirname } from "node:path";
|
|
19
19
|
import { fileURLToPath } from "node:url";
|
|
20
|
+
import { createRequire } from "node:module";
|
|
20
21
|
|
|
21
22
|
/** ASF's package root (this file is <root>/lib/code-health/tools.mjs). */
|
|
22
23
|
export const ASF_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
@@ -38,14 +39,66 @@ function isExecutable(p) {
|
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Locate an installed package's root directory, the way node itself would.
|
|
44
|
+
*
|
|
45
|
+
* A literal `<base>/node_modules/<name>` check is wrong for the layout pi
|
|
46
|
+
* actually installs: npm HOISTS pi-aia-asf's dependencies into a shared
|
|
47
|
+
* node_modules one level up, so the package dir has no node_modules of its own
|
|
48
|
+
* and every tool looked missing — which silently fell through to a system eslint
|
|
49
|
+
* (v8, no flat config) and to eight skips again. require.resolve walks up the
|
|
50
|
+
* real module paths, so it finds the hoisted copy.
|
|
51
|
+
*/
|
|
52
|
+
function findPkgDir(base, name) {
|
|
53
|
+
// The ancestor walk comes FIRST: it is exactly node's own node_modules
|
|
54
|
+
// resolution, and require.resolve can escape the install entirely (a Debian
|
|
55
|
+
// NODE_PATH found /usr/share/nodejs/eslint before the copy that shipped with
|
|
56
|
+
// pi-aia-asf — the gate would then measure with eslint 8 and fail to run).
|
|
57
|
+
let dir2 = base;
|
|
58
|
+
for (let i = 0; i < 8; i++) {
|
|
59
|
+
const candidate = join(dir2, "node_modules", name);
|
|
60
|
+
if (existsSync(join(candidate, "package.json"))) {
|
|
61
|
+
try {
|
|
62
|
+
return { dir: candidate, meta: JSON.parse(readFileSync(join(candidate, "package.json"), "utf-8")) };
|
|
63
|
+
} catch { return { dir: candidate, meta: {} }; }
|
|
64
|
+
}
|
|
65
|
+
const up = dirname(dir2);
|
|
66
|
+
if (up === dir2) break;
|
|
67
|
+
dir2 = up;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const req = createRequire(join(base, "package.json"));
|
|
71
|
+
let dir = dirname(req.resolve(name));
|
|
72
|
+
for (let i = 0; i < 6; i++) {
|
|
73
|
+
const pj = join(dir, "package.json");
|
|
74
|
+
if (existsSync(pj)) {
|
|
75
|
+
try {
|
|
76
|
+
const meta = JSON.parse(readFileSync(pj, "utf-8"));
|
|
77
|
+
if (meta.name === name) return { dir, meta };
|
|
78
|
+
} catch { /* keep walking */ }
|
|
79
|
+
}
|
|
80
|
+
const up = dirname(dir);
|
|
81
|
+
if (up === dir) break;
|
|
82
|
+
dir = up;
|
|
83
|
+
}
|
|
84
|
+
} catch { /* packages with no `main` (jscpd) cannot be resolved by name */ }
|
|
85
|
+
const literal = join(base, "node_modules", name);
|
|
86
|
+
if (existsSync(literal)) {
|
|
87
|
+
try {
|
|
88
|
+
return { dir: literal, meta: JSON.parse(readFileSync(join(literal, "package.json"), "utf-8")) };
|
|
89
|
+
} catch { return { dir: literal, meta: {} }; }
|
|
90
|
+
}
|
|
91
|
+
return null;
|
|
44
92
|
}
|
|
45
93
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
94
|
+
/** The executable a package declares in its own `bin` field. */
|
|
95
|
+
function binFromPkg(found, binName) {
|
|
96
|
+
if (!found) return null;
|
|
97
|
+
const bin = found.meta?.bin;
|
|
98
|
+
const rel = typeof bin === "string" ? bin : bin?.[binName];
|
|
99
|
+
if (!rel) return null;
|
|
100
|
+
const p = join(found.dir, rel);
|
|
101
|
+
return isExecutable(p) ? p : null;
|
|
49
102
|
}
|
|
50
103
|
|
|
51
104
|
function findOnPath(name) {
|
|
@@ -67,9 +120,9 @@ function findOnPath(name) {
|
|
|
67
120
|
* bins:Record<string,string|null>, pkgs:Record<string,string|null>,
|
|
68
121
|
* sources:Record<string,string>}}
|
|
69
122
|
*/
|
|
70
|
-
export function resolveTools(projectRoot) {
|
|
123
|
+
export function resolveTools(projectRoot, { asfRoot = ASF_ROOT } = {}) {
|
|
71
124
|
const bases = [
|
|
72
|
-
{ dir:
|
|
125
|
+
{ dir: asfRoot, source: "asf" },
|
|
73
126
|
{ dir: projectRoot, source: "project" },
|
|
74
127
|
];
|
|
75
128
|
const bins = {};
|
|
@@ -80,7 +133,7 @@ export function resolveTools(projectRoot) {
|
|
|
80
133
|
if (spec.bin) {
|
|
81
134
|
bins[key] = null;
|
|
82
135
|
for (const b of bases) {
|
|
83
|
-
const found =
|
|
136
|
+
const found = binFromPkg(findPkgDir(b.dir, spec.pkg), spec.bin);
|
|
84
137
|
if (found) {
|
|
85
138
|
bins[key] = found;
|
|
86
139
|
sources[key] = b.source;
|
|
@@ -98,9 +151,9 @@ export function resolveTools(projectRoot) {
|
|
|
98
151
|
if (spec.pkg) {
|
|
99
152
|
pkgs[key] = null;
|
|
100
153
|
for (const b of bases) {
|
|
101
|
-
const found =
|
|
154
|
+
const found = findPkgDir(b.dir, spec.pkg);
|
|
102
155
|
if (found) {
|
|
103
|
-
pkgs[key] = found;
|
|
156
|
+
pkgs[key] = found.dir;
|
|
104
157
|
if (!sources[key]) sources[key] = b.source;
|
|
105
158
|
break;
|
|
106
159
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-aia-asf",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.3",
|
|
4
4
|
"description": "Ai Applied Agentic Software Factory — codifies the full software development flow: intake, research, spec capture, adversarial analysis, planning with approval gates, test-first implementation, and release. Requires pi-vigilant, pi-smart-web-search, pi-smart-fetch, and pi-aia-browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|