agentic-workflow-manager 9.4.1 → 9.6.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.
Files changed (57) hide show
  1. package/dist/src/commands/preflight/checks.js +58 -27
  2. package/dist/src/commands/sensors/bootstrap.js +171 -0
  3. package/dist/src/commands/sensors/compatibility/contract.js +18 -15
  4. package/dist/src/commands/sensors/compatibility/manifest.js +75 -2
  5. package/dist/src/commands/sensors/compatibility/materialize.js +256 -54
  6. package/dist/src/commands/sensors/compatibility/pack-source.js +61 -17
  7. package/dist/src/commands/sensors/compatibility/safe-file.js +286 -0
  8. package/dist/src/commands/sensors/compatibility/source.js +75 -0
  9. package/dist/src/commands/sensors/coverage/index.js +2 -2
  10. package/dist/src/commands/sensors/coverage/resolve.js +35 -29
  11. package/dist/src/commands/sensors/detection.js +31 -0
  12. package/dist/src/commands/sensors/exec.js +116 -11
  13. package/dist/src/commands/sensors/index.js +51 -10
  14. package/dist/src/commands/sensors/init.js +17 -168
  15. package/dist/src/commands/sensors/migrate.js +209 -0
  16. package/dist/src/commands/sensors/project.js +107 -0
  17. package/dist/src/commands/sensors/run.js +66 -47
  18. package/dist/src/commands/sensors/status.js +97 -56
  19. package/dist/src/core/init/steps.js +24 -11
  20. package/dist/src/core/secure-fs/native-bridge.js +209 -0
  21. package/dist/tests/commands/preflight/preflight.test.js +31 -0
  22. package/dist/tests/commands/sensors/bootstrap-observational.test.js +24 -0
  23. package/dist/tests/commands/sensors/bootstrap.test.js +175 -0
  24. package/dist/tests/commands/sensors/compatibility/contract.test.js +38 -0
  25. package/dist/tests/commands/sensors/compatibility/manifest.test.js +90 -0
  26. package/dist/tests/commands/sensors/compatibility/materialize.test.js +410 -0
  27. package/dist/tests/commands/sensors/compatibility/pack-source.test.js +249 -0
  28. package/dist/tests/commands/sensors/compatibility/safe-file.test.js +52 -0
  29. package/dist/tests/commands/sensors/compatibility/source.test.js +121 -0
  30. package/dist/tests/commands/sensors/coverage/index.test.js +20 -0
  31. package/dist/tests/commands/sensors/coverage/resolve.test.js +36 -34
  32. package/dist/tests/commands/sensors/exec.test.js +86 -0
  33. package/dist/tests/commands/sensors/index.test.js +54 -0
  34. package/dist/tests/commands/sensors/init-pack-unavailable.test.js +21 -84
  35. package/dist/tests/commands/sensors/init.test.js +56 -490
  36. package/dist/tests/commands/sensors/migrate.test.js +513 -0
  37. package/dist/tests/commands/sensors/project.test.js +102 -0
  38. package/dist/tests/commands/sensors/run.test.js +107 -4
  39. package/dist/tests/commands/sensors/status.test.js +106 -2
  40. package/dist/tests/core/init/orchestrator.test.js +3 -1
  41. package/dist/tests/core/secure-fs/native-bridge.test.js +733 -0
  42. package/dist/tests/integration/preflight-json-pipe.e2e.test.js +5 -0
  43. package/dist/tests/integration/sensor-bootstrap.e2e.test.js +165 -0
  44. package/dist/tests/integration/sensor-compatibility.e2e.test.js +14 -23
  45. package/dist/tests/integration/sensor-coverage.e2e.test.js +15 -24
  46. package/dist/tests/integration/sensor-structured-run.e2e.test.js +3 -1
  47. package/dist/tests/structural/native-release-artifacts.test.js +22 -0
  48. package/dist/tests/structural/release-flags-are-reachable-from-ci.test.js +10 -0
  49. package/dist/tests/structural/release-registry-provenance.test.js +4 -0
  50. package/dist/tests/structural/sensor-portability-contract.test.js +44 -0
  51. package/package.json +6 -2
  52. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  53. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  54. package/prebuilds/linux-arm64/secure_fs.node +0 -0
  55. package/prebuilds/linux-x64/secure_fs.node +0 -0
  56. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  57. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.planV2Migration = planV2Migration;
7
+ exports.replaceV2ManifestWithV3 = replaceV2ManifestWithV3;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const contract_1 = require("./compatibility/contract");
11
+ const manifest_1 = require("./compatibility/manifest");
12
+ const safe_file_1 = require("./compatibility/safe-file");
13
+ function isRecord(value) {
14
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
15
+ }
16
+ function sameSensorField(v2, v3, pick) {
17
+ const names = Object.keys(v2.sensors);
18
+ return names.length === Object.keys(v3.sensors).length
19
+ && names.every(name => name in v3.sensors && JSON.stringify(pick(v2.sensors[name])) === JSON.stringify(pick(v3.sensors[name])));
20
+ }
21
+ function compareV2Semantics(v2, v3, registry) {
22
+ return Object.freeze({
23
+ pack: v2.pack === v3.pack && v2.packSelection === v3.packSelection,
24
+ enabledDisabled: sameSensorField(v2, v3, sensor => sensor.enabled),
25
+ structuredCommands: sameSensorField(v2, v3, sensor => sensor.command),
26
+ assets: sameSensorField(v2, v3, sensor => sensor.assets),
27
+ timeouts: sameSensorField(v2, v3, sensor => sensor.timeout),
28
+ fast: sameSensorField(v2, v3, sensor => sensor.fast),
29
+ variants: sameSensorField(v2, v3, sensor => sensor.variantId),
30
+ policies: sameSensorField(v2, v3, sensor => sensor.policyRef),
31
+ sensorIdentityAndOrder: JSON.stringify(Object.keys(v2.sensors)) === JSON.stringify(Object.keys(v3.sensors)),
32
+ concurrency: v2.concurrency === v3.concurrency,
33
+ compatibilityEvidence: sameSensorField(v2, v3, sensor => sensor.initializedCompatibility),
34
+ packageRoot: v2.packageRoot === v3.packageRoot,
35
+ logicalSourceBinding: v3.source.registry === registry,
36
+ });
37
+ }
38
+ function allEquivalent(report) {
39
+ return Object.values(report).every(value => value === true);
40
+ }
41
+ function exactLogicalSource(input, pack) {
42
+ if (!isRecord(input))
43
+ throw new Error('v2 migration source is required');
44
+ if (input.kind === 'source-unavailable')
45
+ throw new Error('v2 migration source is unavailable');
46
+ if (input.kind === 'source-ambiguous')
47
+ throw new Error('v2 migration source is ambiguous');
48
+ if ((input.kind !== 'logical' && input.kind !== 'legacy-rebound') || !isRecord(input.source))
49
+ throw new Error('v2 migration source must be a unique logical resolution');
50
+ const source = input.source;
51
+ if (Object.keys(source).some(key => !['path', 'content', 'registry'].includes(key))
52
+ || typeof source.path !== 'string' || typeof source.content !== 'string' || !isRecord(source.registry)) {
53
+ throw new Error('v2 migration source must contain an exact resolved pack');
54
+ }
55
+ const registry = source.registry;
56
+ if (Object.keys(registry).some(key => !['name', 'remote', 'contentRoot'].includes(key))
57
+ || typeof registry.name !== 'string' || !/^[a-z][a-z0-9-]*$/.test(registry.name)
58
+ || typeof registry.remote !== 'string' || registry.remote.trim() === '' || /[\0\r\n]/.test(registry.remote)
59
+ || typeof registry.contentRoot !== 'string' || !path_1.default.isAbsolute(registry.contentRoot) || path_1.default.normalize(registry.contentRoot) !== registry.contentRoot) {
60
+ throw new Error('v2 migration source registry must be a stable logical registry');
61
+ }
62
+ // `resolveSensorSource` already selected and read this exact source. The
63
+ // migration planner consumes that immutable snapshot; re-canonicalizing its
64
+ // host directory would make a no-write plan depend on later filesystem state.
65
+ const expectedPath = path_1.default.join(registry.contentRoot, 'sensor-packs', pack, 'pack.json');
66
+ if (source.path !== expectedPath)
67
+ throw new Error('v2 migration source must contain an exact resolved pack');
68
+ let parsedSource;
69
+ try {
70
+ parsedSource = (0, contract_1.parseSensorPack)(JSON.parse(source.content), 'v2 migration resolved source');
71
+ }
72
+ catch {
73
+ throw new Error('v2 migration source exact v2 pack is invalid');
74
+ }
75
+ if (parsedSource.kind !== 'v2' || parsedSource.pack.name !== pack)
76
+ throw new Error('v2 migration source must contain the exact v2 pack');
77
+ return source;
78
+ }
79
+ function assertSourceCompatibleWithManifest(source, manifest) {
80
+ let parsedSource;
81
+ try {
82
+ parsedSource = (0, contract_1.parseSensorPack)(JSON.parse(source.content), 'v2 migration resolved source');
83
+ }
84
+ catch {
85
+ throw new Error('v2 migration source exact v2 pack is invalid');
86
+ }
87
+ if (parsedSource.kind !== 'v2' || parsedSource.pack.name !== manifest.pack) {
88
+ throw new Error('v2 migration source must contain the exact v2 pack');
89
+ }
90
+ for (const [name, sensor] of Object.entries(manifest.sensors)) {
91
+ const selected = parsedSource.pack.sensors[name];
92
+ if (!selected || !selected.variants.some(variant => variant.id === sensor.variantId)) {
93
+ throw new Error('v2 migration source is not compatible with selected manifest sensors');
94
+ }
95
+ }
96
+ }
97
+ function hasPhysicalSensorPath(value, registryRoots) {
98
+ if (typeof value === 'string') {
99
+ const normalized = path_1.default.posix.normalize(value.replace(/\\/g, '/')).toLowerCase();
100
+ const withoutWebUrls = value.replace(/https?:\/\/[^\s"']+/gi, '');
101
+ const roots = registryRoots
102
+ .filter((root) => typeof root === 'string' && root.length > 0)
103
+ .map(root => path_1.default.posix.normalize(root.replace(/\\/g, '/')).toLowerCase());
104
+ const hasAbsolutePathAtTokenBoundary = /(?:^|[^A-Za-z0-9_.~\/\\-])["']?(?:\/|\\|[A-Za-z]:|\/\/)/.test(withoutWebUrls);
105
+ return roots.some(root => normalized.includes(root))
106
+ || hasAbsolutePathAtTokenBoundary;
107
+ }
108
+ return Array.isArray(value) ? value.some(item => hasPhysicalSensorPath(item, registryRoots))
109
+ : isRecord(value) && Object.values(value).some(item => hasPhysicalSensorPath(item, registryRoots));
110
+ }
111
+ function inspectManifest(manifestPath, failure) {
112
+ let stat;
113
+ try {
114
+ stat = fs_1.default.lstatSync(manifestPath, { bigint: true });
115
+ }
116
+ catch {
117
+ throw failure('open');
118
+ }
119
+ if (!stat.isFile() || stat.isSymbolicLink())
120
+ throw failure('regular');
121
+ return { stat, ...(0, safe_file_1.readInspectedBoundedFileWithIdentity)(manifestPath, stat, 1024 * 1024, failure) };
122
+ }
123
+ function assertManifestUnchanged(manifestPath, original) {
124
+ const changed = () => new Error('v2 migration original manifest changed before commit');
125
+ const current = inspectManifest(manifestPath, () => changed());
126
+ if (current.stat.dev !== original.stat.dev || current.stat.ino !== original.stat.ino
127
+ || current.stat.size !== original.stat.size || !current.content.equals(original.content)) {
128
+ throw changed();
129
+ }
130
+ }
131
+ /** Build, but never persist, a portable v3 replacement for a validated v2 manifest. */
132
+ function planV2Migration(input) {
133
+ if (!input || typeof input !== 'object')
134
+ throw new Error('v2 migration input is required');
135
+ const parsed = (0, manifest_1.parseSensorManifest)(input.manifest, 'v2 migration manifest');
136
+ if (parsed.kind !== 'v2')
137
+ throw new Error('v2 migration requires a v2 project-sensors manifest');
138
+ const source = exactLogicalSource(input.source, parsed.pack.pack);
139
+ assertSourceCompatibleWithManifest(source, parsed.pack);
140
+ if (hasPhysicalSensorPath(parsed.pack.sensors, [parsed.pack.registryRoot, source.registry.contentRoot])
141
+ || hasPhysicalSensorPath(parsed.pack.packageRoot, []))
142
+ throw new Error('v2 migration sensor semantics contain a physical path');
143
+ const candidate = {
144
+ schemaVersion: 3,
145
+ mode: 'project-sensors',
146
+ pack: parsed.pack.pack,
147
+ ...(parsed.pack.packSelection === 'explicit' ? { packSelection: 'explicit' } : {}),
148
+ source: { registry: source.registry.name },
149
+ ...(parsed.pack.packageRoot ? { packageRoot: parsed.pack.packageRoot } : {}),
150
+ sensors: parsed.pack.sensors,
151
+ ...(parsed.pack.concurrency ? { concurrency: parsed.pack.concurrency } : {}),
152
+ };
153
+ const validated = (0, manifest_1.parseSensorManifest)(candidate, 'v2 migration candidate');
154
+ if (validated.kind !== 'v3' || validated.pack.mode !== 'project-sensors') {
155
+ throw new Error('v2 migration candidate semantic mismatch');
156
+ }
157
+ const equivalence = compareV2Semantics(parsed.pack, validated.pack, source.registry.name);
158
+ if (!allEquivalent(equivalence))
159
+ throw new Error('v2 migration candidate semantic mismatch');
160
+ return { candidate: validated.pack, equivalent: true, equivalence };
161
+ }
162
+ /** Validate the old and new durable contracts, then atomically replace only the manifest. */
163
+ function replaceV2ManifestWithV3(manifestPath, candidate, source) {
164
+ if (typeof manifestPath !== 'string' || !path_1.default.isAbsolute(manifestPath) || path_1.default.normalize(manifestPath) !== manifestPath
165
+ || manifestPath.includes('\0') || path_1.default.basename(manifestPath) !== 'sensors.json'
166
+ || path_1.default.basename(path_1.default.dirname(manifestPath)) !== '.awm') {
167
+ throw new Error('v2 migration manifest path must be the canonical project manifest <project>/.awm/sensors.json');
168
+ }
169
+ const manifestDirectory = path_1.default.dirname(manifestPath);
170
+ const projectRoot = path_1.default.dirname(manifestDirectory);
171
+ const destination = '.awm/sensors.json';
172
+ (0, safe_file_1.withProjectLease)(projectRoot, () => {
173
+ const originalFailure = (_reason) => new Error('v2 migration original manifest must be readable JSON');
174
+ let inspected;
175
+ let original;
176
+ try {
177
+ inspected = inspectManifest(manifestPath, originalFailure);
178
+ original = JSON.parse(inspected.content.toString('utf8'));
179
+ }
180
+ catch {
181
+ throw new Error('v2 migration original manifest must be readable JSON');
182
+ }
183
+ const before = (0, manifest_1.parseSensorManifest)(original, manifestPath);
184
+ const after = (0, manifest_1.parseSensorManifest)(candidate, 'v2 migration candidate');
185
+ if (before.kind !== 'v2' || after.kind !== 'v3' || after.pack.mode !== 'project-sensors') {
186
+ throw new Error('v2 migration candidate semantic mismatch');
187
+ }
188
+ const logicalSource = exactLogicalSource(source, before.pack.pack);
189
+ assertSourceCompatibleWithManifest(logicalSource, before.pack);
190
+ if (after.pack.source.registry !== logicalSource.registry.name) {
191
+ throw new Error('v2 migration candidate semantic mismatch');
192
+ }
193
+ if (hasPhysicalSensorPath(before.pack.sensors, [before.pack.registryRoot, logicalSource.registry.contentRoot])
194
+ || hasPhysicalSensorPath(after.pack.sensors, [before.pack.registryRoot, logicalSource.registry.contentRoot])
195
+ || hasPhysicalSensorPath(before.pack.packageRoot, []) || hasPhysicalSensorPath(after.pack.packageRoot, [])) {
196
+ throw new Error('v2 migration candidate contains a physical path');
197
+ }
198
+ if (!allEquivalent(compareV2Semantics(before.pack, after.pack, logicalSource.registry.name))) {
199
+ throw new Error('v2 migration candidate semantic mismatch');
200
+ }
201
+ // The native implementation reopens every ancestor without following links,
202
+ // verifies these exact bytes at the publication fence, and preserves a
203
+ // competing target instead of replacing it.
204
+ assertManifestUnchanged(manifestPath, inspected);
205
+ (0, safe_file_1.writeProjectFile)(projectRoot, destination, Buffer.from((0, manifest_1.serializeManifestV3)(after.pack), 'utf8'), {
206
+ mode: 'replace', expected: inspected.content, expectedIdentity: inspected.identity, createParents: false,
207
+ });
208
+ });
209
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveSensorProject = resolveSensorProject;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const util_1 = require("util");
10
+ const manifest_1 = require("./compatibility/manifest");
11
+ function regularManifest(manifestPath) {
12
+ try {
13
+ const stat = fs_1.default.lstatSync(manifestPath);
14
+ return stat.isFile() && !stat.isSymbolicLink();
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ function gitMarker(directory) {
21
+ try {
22
+ const stat = fs_1.default.lstatSync(path_1.default.join(directory, '.git'));
23
+ return stat.isDirectory() || stat.isFile();
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ function manifestResolution(projectRoot) {
30
+ const manifestPath = path_1.default.join(projectRoot, '.awm', 'sensors.json');
31
+ try {
32
+ fs_1.default.lstatSync(manifestPath);
33
+ }
34
+ catch (error) {
35
+ if (error.code === 'ENOENT')
36
+ return { state: 'missing', projectRoot, manifestPath };
37
+ return { state: 'invalid', projectRoot, manifestPath, reason: 'sensor manifest cannot be inspected' };
38
+ }
39
+ if (!regularManifest(manifestPath))
40
+ return { state: 'invalid', projectRoot, manifestPath, reason: 'sensor manifest must be a regular file' };
41
+ try {
42
+ let text;
43
+ try {
44
+ text = new util_1.TextDecoder('utf-8', { fatal: true }).decode(fs_1.default.readFileSync(manifestPath));
45
+ }
46
+ catch {
47
+ return { state: 'invalid', projectRoot, manifestPath, reason: 'sensor manifest must be valid UTF-8' };
48
+ }
49
+ const manifest = (0, manifest_1.parseSensorManifest)(JSON.parse(text), manifestPath);
50
+ let configuredPackageRoot;
51
+ if (manifest.kind === 'v2')
52
+ configuredPackageRoot = manifest.pack.packageRoot;
53
+ if (manifest.kind === 'v3' && manifest.pack.mode === 'project-sensors')
54
+ configuredPackageRoot = manifest.pack.packageRoot;
55
+ const packageRoot = configuredPackageRoot ? path_1.default.resolve(projectRoot, configuredPackageRoot) : projectRoot;
56
+ if (!fs_1.default.existsSync(packageRoot) || !fs_1.default.statSync(packageRoot).isDirectory()) {
57
+ return { state: 'invalid', projectRoot, manifestPath, reason: 'packageRoot must name an existing directory' };
58
+ }
59
+ const realProjectRoot = fs_1.default.realpathSync(projectRoot);
60
+ const realPackageRoot = fs_1.default.realpathSync(packageRoot);
61
+ const relative = path_1.default.relative(realProjectRoot, realPackageRoot);
62
+ if (relative === '..' || relative.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(relative)) {
63
+ return { state: 'invalid', projectRoot, manifestPath, reason: 'packageRoot escapes the manifest directory' };
64
+ }
65
+ return { state: 'configured', projectRoot, manifestPath, packageRoot, manifest };
66
+ }
67
+ catch (error) {
68
+ return { state: 'invalid', projectRoot, manifestPath, reason: error instanceof Error ? error.message : 'sensor manifest could not be parsed' };
69
+ }
70
+ }
71
+ /** Resolves one manifest authority, never crossing a Git worktree boundary. */
72
+ function resolveSensorProject(startCwd) {
73
+ if (typeof startCwd !== 'string' || startCwd.length === 0)
74
+ throw new Error('startCwd must be a nonempty directory path');
75
+ let directory;
76
+ try {
77
+ directory = fs_1.default.realpathSync(path_1.default.resolve(startCwd));
78
+ }
79
+ catch {
80
+ throw new Error(`startCwd does not exist: ${startCwd}`);
81
+ }
82
+ if (!fs_1.default.statSync(directory).isDirectory())
83
+ throw new Error(`startCwd is not a directory: ${startCwd}`);
84
+ let boundary = null;
85
+ let probe = directory;
86
+ while (true) {
87
+ if (gitMarker(probe)) {
88
+ boundary = probe;
89
+ break;
90
+ }
91
+ const parent = path_1.default.dirname(probe);
92
+ if (parent === probe)
93
+ break;
94
+ probe = parent;
95
+ }
96
+ if (boundary === null)
97
+ return manifestResolution(directory);
98
+ let current = directory;
99
+ while (true) {
100
+ const resolved = manifestResolution(current);
101
+ if (resolved.state !== 'missing')
102
+ return resolved;
103
+ if (current === boundary)
104
+ return resolved;
105
+ current = path_1.default.dirname(current);
106
+ }
107
+ }
@@ -4,11 +4,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.applyBaseline = void 0;
7
- exports.detectPackDrift = detectPackDrift;
8
7
  exports.findManifestDir = findManifestDir;
8
+ exports.detectPackDrift = detectPackDrift;
9
9
  exports.resolveConcurrency = resolveConcurrency;
10
10
  exports.runSensors = runSensors;
11
- const fs_1 = __importDefault(require("fs"));
12
11
  const os_1 = __importDefault(require("os"));
13
12
  const path_1 = __importDefault(require("path"));
14
13
  const baseline_1 = require("./baseline");
@@ -17,25 +16,36 @@ Object.defineProperty(exports, "applyBaseline", { enumerable: true, get: functio
17
16
  const changed_1 = require("./changed");
18
17
  // Solo `detectStack` (puro, lee el arbol) — NO `initSensors`, que escribe. `run` es un
19
18
  // verbo de lectura: no debe tener a mano ninguna funcion capaz de mutar el proyecto.
20
- const init_1 = require("./init");
21
- const manifest_1 = require("./compatibility/manifest");
19
+ const detection_1 = require("./detection");
22
20
  const live_1 = require("./compatibility/live");
21
+ const contract_1 = require("./compatibility/contract");
22
+ const source_1 = require("./compatibility/source");
23
+ const registries_1 = require("../../core/registries");
23
24
  const prepare_1 = require("./prepare");
24
25
  const verdict_1 = require("./verdict");
25
- const MANIFEST_FILE = '.awm/sensors.json';
26
- const DEFAULT_FAST_TIMEOUT = 10_000;
27
- const DEFAULT_SLOW_TIMEOUT = 120_000;
26
+ const project_1 = require("./project");
28
27
  // Sensor JSON output can be several MB on large repos (e.g. `eslint --format json`
29
28
  // with thousands of findings). A 1MB cap killed the child with SIGTERM when
30
29
  // exceeded — which previously surfaced as a false "timeout".
31
30
  const MAX_BUFFER = 64 * 1024 * 1024;
32
31
  /** Hard ceiling on parallel sensors: past this, they only contend for the same cores. */
33
32
  const MAX_CONCURRENCY = 4;
34
- async function resolveLiveV2(cwd, manifest) {
35
- if (manifest.kind !== 'v2')
33
+ async function resolveLiveFromSource(cwd, source, packSelection) {
34
+ try {
35
+ const parsed = (0, contract_1.parseSensorPack)(JSON.parse(source.content), source.path);
36
+ if (parsed.kind !== 'v2')
37
+ return null;
38
+ return await (0, live_1.resolveParsedPackCompatibility)(cwd, parsed.pack, { packSelection });
39
+ }
40
+ catch {
36
41
  return null;
42
+ }
43
+ }
44
+ /** Compatibility adapter retained for the baseline command; authority selection is shared. */
45
+ function findManifestDir(startCwd) {
37
46
  try {
38
- return await (0, live_1.resolveLiveCompatibility)(cwd, manifest.pack.pack, manifest.pack.registryRoot, { packSelection: manifest.pack.packSelection });
47
+ const project = (0, project_1.resolveSensorProject)(startCwd);
48
+ return project.state === 'configured' ? project.projectRoot : null;
39
49
  }
40
50
  catch {
41
51
  return null;
@@ -57,7 +67,7 @@ async function resolveLiveV2(cwd, manifest) {
57
67
  * and failed on by preflight's `pack` check.
58
68
  */
59
69
  function detectPackDrift(manifestDir, manifest) {
60
- const detection = (0, init_1.detectStack)(manifestDir);
70
+ const detection = (0, detection_1.detectStack)(manifestDir);
61
71
  if (manifest.pack !== 'generic' || detection.pack === 'generic')
62
72
  return { detection };
63
73
  return {
@@ -69,26 +79,6 @@ function detectPackDrift(manifestDir, manifest) {
69
79
  },
70
80
  };
71
81
  }
72
- /**
73
- * Walk up from `startCwd` looking for the nearest ancestor that contains
74
- * `.awm/sensors.json` (git/.git pattern). Returns that directory, or null
75
- * if none is found before the filesystem root.
76
- */
77
- function findManifestDir(startCwd) {
78
- let dir = path_1.default.resolve(startCwd);
79
- while (true) {
80
- if (fs_1.default.existsSync(path_1.default.join(dir, MANIFEST_FILE)))
81
- return dir;
82
- // Temp directories are test/scratch boundaries, never project ancestors.
83
- // This prevents an unrelated /tmp/.awm from being adopted by a fresh project.
84
- if (dir === path_1.default.resolve(os_1.default.tmpdir()))
85
- return null;
86
- const parent = path_1.default.dirname(dir);
87
- if (parent === dir)
88
- return null; // reached filesystem root
89
- dir = parent;
90
- }
91
- }
92
82
  function shouldRun(isFast, opts) {
93
83
  if (opts.all)
94
84
  return true;
@@ -135,15 +125,38 @@ async function pooled(tasks, limit) {
135
125
  */
136
126
  async function runSensors(opts = {}) {
137
127
  (0, prepare_1.validateRunOptions)(opts);
138
- const manifestDir = findManifestDir(opts.cwd ?? process.cwd());
139
- if (!manifestDir)
140
- return { sensors: [], overall: 'not_certified' };
141
- let parsed;
128
+ let project;
142
129
  try {
143
- parsed = (0, manifest_1.parseSensorManifest)(JSON.parse(fs_1.default.readFileSync(path_1.default.join(manifestDir, MANIFEST_FILE), 'utf8')), path_1.default.join(manifestDir, MANIFEST_FILE));
130
+ project = (0, project_1.resolveSensorProject)(opts.cwd ?? process.cwd());
144
131
  }
145
132
  catch {
146
- return { sensors: [], overall: 'not_certified' };
133
+ const root = typeof opts.cwd === 'string' && opts.cwd.trim() !== '' ? path_1.default.resolve(opts.cwd) : process.cwd();
134
+ return { sensors: [], overall: 'not_certified', mode: 'invalid', reason: 'invalid-start-cwd', projectRoot: root, manifestPath: path_1.default.join(root, '.awm', 'sensors.json'), remedy: 'provide-an-existing-project-directory' };
135
+ }
136
+ if (project.state === 'missing') {
137
+ return { sensors: [], overall: 'not_certified', mode: 'missing', reason: 'manifest-absent', projectRoot: project.projectRoot, manifestPath: project.manifestPath, remedy: 'run-awm-sensors-bootstrap' };
138
+ }
139
+ if (project.state === 'invalid') {
140
+ const reason = project.reason.includes('unsupported manifest schemaVersion') ? 'schema-unsupported' : 'manifest-malformed';
141
+ return { sensors: [], overall: 'not_certified', mode: 'invalid', reason, projectRoot: project.projectRoot, manifestPath: project.manifestPath, remedy: 'repair-sensor-manifest' };
142
+ }
143
+ const manifestDir = project.projectRoot;
144
+ const parsed = project.manifest;
145
+ const authority = { projectRoot: project.projectRoot, manifestPath: project.manifestPath };
146
+ if (parsed.kind === 'v3' && (parsed.pack.mode === 'native-gate' || parsed.pack.mode === 'opt-out')) {
147
+ return { sensors: [], overall: 'not_certified', ...authority, mode: parsed.pack.mode, reason: `${parsed.pack.mode}-declared`, declarationReason: parsed.pack.reason };
148
+ }
149
+ let resolvedSource;
150
+ if (parsed.kind === 'v2' || parsed.kind === 'v3') {
151
+ try {
152
+ resolvedSource = (0, source_1.resolveSensorSource)(parsed, { registries: (0, registries_1.listRegistries)() });
153
+ }
154
+ catch {
155
+ return { sensors: [], overall: 'not_certified', ...authority, mode: 'invalid', reason: 'sensor-source-invalid', remedy: 'repair-or-run-awm-update' };
156
+ }
157
+ if (resolvedSource.kind === 'source-unavailable' || resolvedSource.kind === 'source-ambiguous') {
158
+ return { sensors: [], overall: 'not_certified', ...authority, mode: resolvedSource.kind, reason: resolvedSource.reason, remedy: resolvedSource.remedy, source: { kind: resolvedSource.kind, candidates: resolvedSource.kind === 'source-ambiguous' ? resolvedSource.candidates : undefined } };
159
+ }
147
160
  }
148
161
  const baseline = opts.ignoreBaseline ? null : (0, baseline_1.readBaseline)(manifestDir);
149
162
  const changed = opts.changed ? (0, changed_1.changedFiles)(manifestDir, opts.base ?? 'HEAD') : null;
@@ -159,22 +172,23 @@ async function runSensors(opts = {}) {
159
172
  // manifest itself stays discoverable at the repo root via findManifestDir.
160
173
  // Legacy manifests never carry this field — manifestDir is always correct
161
174
  // for them, unchanged.
162
- const projectCwd = parsed.kind === 'v2' && parsed.pack.packageRoot
163
- ? path_1.default.resolve(manifestDir, parsed.pack.packageRoot)
164
- : manifestDir;
165
- const live = parsed.kind === 'v2' ? await resolveLiveV2(projectCwd, parsed) : null;
175
+ const projectCwd = project.packageRoot;
176
+ const live = parsed.kind !== 'legacy' && resolvedSource && 'source' in resolvedSource
177
+ ? await resolveLiveFromSource(projectCwd, resolvedSource.source, 'packSelection' in parsed.pack ? parsed.pack.packSelection : undefined)
178
+ : null;
166
179
  const drift = parsed.kind === 'legacy' ? detectPackDrift(manifestDir, parsed.pack) : undefined;
167
180
  const requestedScope = opts.changed ? 'changed' : 'full';
168
181
  const prepared = [];
169
- for (const [name, sensor] of Object.entries(parsed.pack.sensors)) {
170
- const liveSensor = parsed.kind === 'v2' ? live?.pack.sensors[name] : undefined;
171
- const fast = parsed.kind === 'v2'
182
+ const activeManifest = parsed.pack;
183
+ for (const [name, sensor] of Object.entries(activeManifest.sensors)) {
184
+ const liveSensor = parsed.kind !== 'legacy' ? live?.pack.sensors[name] : undefined;
185
+ const fast = parsed.kind !== 'legacy'
172
186
  ? sensor.fast ?? liveSensor?.fast ?? false
173
187
  : sensor.fast ?? false;
174
188
  if (!shouldRun(fast, opts))
175
189
  continue;
176
- const execution = parsed.kind === 'v2'
177
- ? (0, prepare_1.prepareV2Sensor)({ name, sensor, liveSensor, liveState: live?.sensors[name], requestedScope, changed: changed ?? undefined })
190
+ const execution = parsed.kind !== 'legacy'
191
+ ? (0, prepare_1.prepareV2Sensor)({ name, sensor: sensor, liveSensor, liveState: live?.sensors[name], requestedScope, changed: changed ?? undefined })
178
192
  : (0, prepare_1.prepareLegacySensor)({ name, config: sensor, requestedScope, changed: changed ?? undefined });
179
193
  prepared.push(sensor.enabled === false
180
194
  ? { ...execution, command: undefined, syntheticStatus: 'skipped', syntheticReason: 'disabled' }
@@ -183,7 +197,7 @@ async function runSensors(opts = {}) {
183
197
  const results = await pooled(prepared.map(entry => async () => {
184
198
  const result = await (0, result_1.executePrepared)(entry, projectCwd);
185
199
  return baseline ? (0, result_1.applyBaseline)(result, baseline[entry.name]) : result;
186
- }), resolveConcurrency(parsed.pack, prepared.length));
200
+ }), resolveConcurrency(activeManifest, prepared.length));
187
201
  let overall = (0, verdict_1.reduceVerdict)(results);
188
202
  // Legacy commands retain operational compatibility but their shell-backed
189
203
  // contract cannot certify a green run.
@@ -194,6 +208,11 @@ async function runSensors(opts = {}) {
194
208
  return {
195
209
  sensors: results,
196
210
  overall,
211
+ ...authority,
212
+ mode: 'project-sensors',
213
+ reason: parsed.kind === 'legacy' ? 'legacy-v1' : resolvedSource?.kind === 'logical' ? 'configured-v3' : resolvedSource?.kind,
214
+ ...(parsed.kind !== 'legacy' && resolvedSource && 'source' in resolvedSource
215
+ ? { source: { kind: resolvedSource.kind, registry: resolvedSource.source.registry.name } } : {}),
197
216
  ...(drift?.drift ? { packDrift: drift.drift } : {}),
198
217
  ...(changed ? { changedScope: { files: changed.files.length, ...(changed.error ? { error: changed.error } : {}) } } : {}),
199
218
  };