agentic-workflow-manager 9.4.0 → 9.5.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.
@@ -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");
@@ -18,24 +17,35 @@ 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
19
  const init_1 = require("./init");
21
- const manifest_1 = require("./compatibility/manifest");
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;
@@ -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
  };
@@ -7,11 +7,12 @@ exports.computeSensorStatus = computeSensorStatus;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const paths_1 = require("../../core/paths");
10
- const manifest_1 = require("./compatibility/manifest");
11
10
  const contract_1 = require("./compatibility/contract");
12
11
  const discovery_1 = require("./compatibility/discovery");
13
- const pack_source_1 = require("./compatibility/pack-source");
14
12
  const resolve_1 = require("./compatibility/resolve");
13
+ const project_1 = require("./project");
14
+ const source_1 = require("./compatibility/source");
15
+ const registries_1 = require("../../core/registries");
15
16
  /** First non-flag token after `npx` — the tool the command actually runs. */
16
17
  function npxTool(parts) {
17
18
  for (let i = 1; i < parts.length; i++) {
@@ -94,10 +95,7 @@ function checkStructuredCommand(command, cwd, assets = []) {
94
95
  * into a health check. A selected variant still proves that its current tool
95
96
  * and runtime ranges are compatible; its probe state remains unverifiable.
96
97
  */
97
- function resolveStaticV2Compatibility(cwd, manifest) {
98
- const source = manifest.registryRoot === undefined
99
- ? (0, pack_source_1.resolvePackSource)(manifest.pack)
100
- : (0, pack_source_1.resolvePackSource)(manifest.pack, { registries: [{ name: 'manifest-provenance', remote: 'local', contentRoot: manifest.registryRoot }] });
98
+ function resolveStaticV2Compatibility(cwd, manifest, source) {
101
99
  const parsed = (0, contract_1.parseSensorPack)(JSON.parse(source.content), source.path);
102
100
  if (parsed.kind !== 'v2')
103
101
  throw new Error(`sensor pack "${manifest.pack}" does not provide a v2 compatibility contract`);
@@ -135,57 +133,104 @@ function staticCompatibilityCheck(sensor, live) {
135
133
  }
136
134
  return null;
137
135
  }
138
- async function computeSensorStatus(cwd = process.cwd()) {
139
- const manifestPath = path_1.default.join(cwd, '.awm', 'sensors.json');
140
- if (!fs_1.default.existsSync(manifestPath)) {
141
- return { overall: 'NOT_CONFIGURED', pack: null, checks: {} };
136
+ function resolvedSourceMetadata(source) {
137
+ return {
138
+ reason: source.kind === 'logical' ? 'configured-v3' : source.kind,
139
+ source: { kind: source.kind, registry: source.source.registry.name },
140
+ };
141
+ }
142
+ function projectMetadata(project, mode, reason, extra = {}) {
143
+ return {
144
+ mode, reason, projectRoot: project.projectRoot, manifestPath: project.manifestPath,
145
+ ...(project.state === 'configured' ? { packageRoot: project.packageRoot } : {}),
146
+ ...extra,
147
+ };
148
+ }
149
+ function sourceFailure(project, pack, source) {
150
+ if (source.kind === 'source-unavailable') {
151
+ return {
152
+ overall: 'DEGRADED', pack, checks: {},
153
+ ...projectMetadata(project, 'source-unavailable', source.reason, { remedy: source.remedy }),
154
+ };
142
155
  }
143
- let manifest;
156
+ return {
157
+ overall: 'DEGRADED', pack, checks: {},
158
+ ...projectMetadata(project, 'source-ambiguous', source.reason, { remedy: source.remedy, candidates: source.candidates }),
159
+ };
160
+ }
161
+ function invalidStatus(project, pack, reason = 'manifest-malformed', remedy = 'repair-sensor-manifest') {
162
+ return {
163
+ overall: 'DEGRADED', pack, checks: {},
164
+ ...projectMetadata(project, 'invalid', reason, { remedy }),
165
+ };
166
+ }
167
+ async function computeSensorStatus(cwd = process.cwd()) {
168
+ let project;
144
169
  try {
145
- const raw = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf-8'));
146
- const parsed = (0, manifest_1.parseSensorManifest)(raw, manifestPath);
147
- if (parsed.kind === 'v2') {
148
- // Monorepo support (mirrors run.ts/init.ts): detection and structured-command
149
- // asset resolution both need to see the real package, not the (possibly
150
- // unrelated) directory the manifest lives in.
151
- const projectCwd = parsed.pack.packageRoot ? path_1.default.resolve(cwd, parsed.pack.packageRoot) : cwd;
152
- const checks = {};
153
- let compatibility;
154
- try {
155
- compatibility = resolveStaticV2Compatibility(projectCwd, parsed.pack);
156
- }
157
- catch (error) {
158
- const detail = error instanceof Error ? error.message : 'live compatibility unavailable';
159
- for (const [name, sensor] of Object.entries(parsed.pack.sensors)) {
160
- checks[name] = sensor.enabled === false ? { ok: true, detail: 'disabled' } : { ok: false, detail };
161
- }
162
- return { overall: 'DEGRADED', pack: parsed.pack.pack, checks };
163
- }
164
- for (const [name, sensor] of Object.entries(parsed.pack.sensors)) {
165
- if (sensor.enabled === false) {
166
- checks[name] = { ok: true, detail: 'disabled' };
167
- continue;
168
- }
169
- checks[name] = staticCompatibilityCheck(sensor, compatibility[name])
170
- ?? checkStructuredCommand(sensor.command, projectCwd, sensor.assets);
171
- }
172
- return { overall: Object.keys(checks).length > 0 && Object.values(checks).every(check => check.ok) ? 'READY' : 'DEGRADED', pack: parsed.pack.pack, checks };
173
- }
174
- manifest = parsed.pack;
170
+ project = (0, project_1.resolveSensorProject)(cwd);
175
171
  }
176
172
  catch {
177
- // Preserve the historic diagnostic path for a hand-edited legacy manifest:
178
- // malformed v2 is fail-closed, while a legacy `sensors:null` is surfaced as
179
- // degraded rather than crashing the status command.
173
+ const root = typeof cwd === 'string' && cwd.trim() !== '' ? path_1.default.resolve(cwd) : process.cwd();
174
+ return { overall: 'DEGRADED', pack: null, checks: {}, mode: 'invalid', reason: 'invalid-start-cwd', projectRoot: root, manifestPath: path_1.default.join(root, '.awm', 'sensors.json'), remedy: 'provide-an-existing-project-directory' };
175
+ }
176
+ if (project.state === 'missing') {
177
+ return { overall: 'NOT_CONFIGURED', pack: null, checks: {}, ...projectMetadata(project, 'missing', 'manifest-absent', { remedy: 'run-awm-sensors-bootstrap' }) };
178
+ }
179
+ if (project.state === 'invalid') {
180
+ let pack = null;
180
181
  try {
181
- const raw = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf-8'));
182
+ const raw = JSON.parse(fs_1.default.readFileSync(project.manifestPath, 'utf-8'));
182
183
  if (raw && typeof raw === 'object' && !Array.isArray(raw) && !('schemaVersion' in raw) && typeof raw.pack === 'string') {
183
- return { overall: 'DEGRADED', pack: raw.pack, checks: {} };
184
+ pack = raw.pack;
184
185
  }
185
186
  }
186
187
  catch { /* malformed JSON is not configured */ }
187
- return { overall: 'NOT_CONFIGURED', pack: null, checks: {} };
188
+ const reason = project.reason.includes('unsupported manifest schemaVersion') ? 'schema-unsupported' : 'manifest-malformed';
189
+ return invalidStatus(project, pack, reason);
188
190
  }
191
+ const packageRoot = project.packageRoot;
192
+ const parsed = project.manifest;
193
+ if (parsed.kind === 'v3' && parsed.pack.mode !== 'project-sensors') {
194
+ return {
195
+ overall: 'NOT_CONFIGURED', pack: null, checks: {},
196
+ ...projectMetadata(project, parsed.pack.mode, `${parsed.pack.mode}-declared`, { declarationReason: parsed.pack.reason }),
197
+ };
198
+ }
199
+ if (parsed.kind === 'v2' || parsed.kind === 'v3') {
200
+ const parsedPack = parsed.pack;
201
+ let resolution;
202
+ try {
203
+ resolution = (0, source_1.resolveSensorSource)(parsed, { registries: (0, registries_1.listRegistries)() });
204
+ }
205
+ catch {
206
+ return invalidStatus(project, parsedPack.pack, 'sensor-source-invalid', 'repair-or-run-awm-update');
207
+ }
208
+ if (!('source' in resolution))
209
+ return sourceFailure(project, parsedPack.pack, resolution);
210
+ const sourceMeta = resolvedSourceMetadata(resolution);
211
+ const manifest = parsedPack;
212
+ const checks = {};
213
+ let compatibility;
214
+ try {
215
+ compatibility = resolveStaticV2Compatibility(packageRoot, manifest, resolution.source);
216
+ }
217
+ catch (error) {
218
+ const detail = error instanceof Error ? error.message : 'live compatibility unavailable';
219
+ for (const [name, sensor] of Object.entries(manifest.sensors))
220
+ checks[name] = sensor.enabled === false ? { ok: true, detail: 'disabled' } : { ok: false, detail };
221
+ return { overall: 'DEGRADED', pack: manifest.pack, checks, ...projectMetadata(project, 'project-sensors', sourceMeta.reason, { ...sourceMeta }) };
222
+ }
223
+ for (const [name, sensor] of Object.entries(manifest.sensors)) {
224
+ checks[name] = sensor.enabled === false ? { ok: true, detail: 'disabled' }
225
+ : staticCompatibilityCheck(sensor, compatibility[name]) ?? checkStructuredCommand(sensor.command, packageRoot, sensor.assets);
226
+ }
227
+ return {
228
+ overall: Object.keys(checks).length > 0 && Object.values(checks).every(check => check.ok) ? 'READY' : 'DEGRADED',
229
+ pack: manifest.pack, checks,
230
+ ...projectMetadata(project, 'project-sensors', sourceMeta.reason, { ...sourceMeta }),
231
+ };
232
+ }
233
+ const manifest = parsed.pack;
189
234
  const checks = {};
190
235
  for (const [name, config] of Object.entries(manifest.sensors ?? {})) {
191
236
  if (config.enabled === false) {
@@ -196,14 +241,10 @@ async function computeSensorStatus(cwd = process.cwd()) {
196
241
  checks[name] = { ok: false, detail: 'no cmd configured' };
197
242
  continue;
198
243
  }
199
- checks[name] = checkCmd(config.cmd, cwd);
200
- }
201
- // `Object.values({}).every(...)` is vacuously true — a manifest with zero sensor
202
- // entries (the registry had no pack.json for this stack; see init.ts) must not read
203
- // as READY just because there was nothing to fail. Same false-green `checkManifest`
204
- // guards against in preflight.
205
- if (Object.keys(manifest.sensors ?? {}).length === 0) {
206
- return { overall: 'DEGRADED', pack: manifest.pack, checks };
244
+ checks[name] = checkCmd(config.cmd, packageRoot);
207
245
  }
208
- return { overall: Object.values(checks).every(check => check.ok) ? 'READY' : 'DEGRADED', pack: manifest.pack, checks };
246
+ return {
247
+ overall: Object.keys(manifest.sensors ?? {}).length > 0 && Object.values(checks).every(check => check.ok) ? 'READY' : 'DEGRADED',
248
+ pack: manifest.pack, checks, ...projectMetadata(project, 'project-sensors', 'legacy-v1'),
249
+ };
209
250
  }
@@ -10,7 +10,7 @@ const config_1 = require("../../utils/config");
10
10
  const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
11
11
  const TIMEOUT_MS = 2_000;
12
12
  const MAX_OUTPUT_BYTES = 64 * 1024;
13
- const NPM_SOURCE = `https://registry.npmjs.org/${cli_version_1.CLI_PACKAGE_NAME}`;
13
+ const NPM_SOURCE = `https://registry.npmjs.org/${cli_version_1.CLI_PACKAGE_NAME}/latest`;
14
14
  const STRICT_SEMVER = /^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
15
15
  function deadline(operation, timeoutMs = TIMEOUT_MS) {
16
16
  let timer;
@@ -106,8 +106,7 @@ async function latestCli(fetchImpl) {
106
106
  const body = JSON.parse(text);
107
107
  if (!body || typeof body !== 'object')
108
108
  return null;
109
- const tags = body['dist-tags'];
110
- const latest = tags && typeof tags === 'object' ? tags.latest : undefined;
109
+ const latest = body.version;
111
110
  return typeof latest === 'string' && parseVersion(latest) ? latest : null;
112
111
  }
113
112
  catch {
@@ -199,8 +198,8 @@ async function checkCurrentness(cwd, deps = {}) {
199
198
  const cli = !parseVersion(installedCli) || !latest || compareStrict(installedCli, latest) === null
200
199
  ? unavailable('cli', NPM_SOURCE, checkedAt, parseVersion(installedCli) ? installedCli : null)
201
200
  : compareStrict(installedCli, latest) === 0
202
- ? { component: 'cli', installed: installedCli, latest, channel: 'stable', source: NPM_SOURCE, checkedAt, status: 'current', detail: 'Installed version equals npm dist-tags.latest.', remedy: 'No action required.' }
203
- : { component: 'cli', installed: installedCli, latest, channel: 'stable', source: NPM_SOURCE, checkedAt, status: 'stale', detail: 'Installed version is behind npm dist-tags.latest.', remedy: `npm i -g ${cli_version_1.CLI_PACKAGE_NAME}@latest && rerun in a fresh process` };
201
+ ? { component: 'cli', installed: installedCli, latest, channel: 'stable', source: NPM_SOURCE, checkedAt, status: 'current', detail: 'Installed version equals the npm latest release.', remedy: 'No action required.' }
202
+ : { component: 'cli', installed: installedCli, latest, channel: 'stable', source: NPM_SOURCE, checkedAt, status: 'stale', detail: 'Installed version is behind the npm latest release.', remedy: `npm i -g ${cli_version_1.CLI_PACKAGE_NAME}@latest && rerun in a fresh process` };
204
203
  const components = [cli];
205
204
  let registries;
206
205
  try {
@@ -718,4 +718,35 @@ describe('preflight', () => {
718
718
  // not the old hardcoded 9 — the shorter id's row must carry visible padding.
719
719
  expect(lines[0]).toMatch(/host {12,}@@marker/);
720
720
  });
721
+ it.each([
722
+ ['native-gate', 'CI is the authority.', 'native_gate', 'native-gate-declared'],
723
+ ['opt-out', 'No local gate is required.', 'ungated', 'opt-out-declared'],
724
+ ])('exposes stable authority metadata for %s', async (mode, declarationReason, status, reason) => {
725
+ const dir = make({ manifest: { schemaVersion: 3, mode, reason: declarationReason } });
726
+ const report = await (0, checks_1.preflight)(dir);
727
+ expect(report).toMatchObject({
728
+ status,
729
+ mode,
730
+ reason,
731
+ projectRoot: dir,
732
+ manifestPath: path_1.default.join(dir, '.awm', 'sensors.json'),
733
+ remedy: expect.any(String),
734
+ });
735
+ expect((0, preflight_1.exitCodeFor)(report)).toBe(1);
736
+ expect(report.status).not.toBe('ready');
737
+ });
738
+ it('derives manifest and tools checks from the same shared authority result', async () => {
739
+ const dir = make({ manifest: '{not valid json' });
740
+ const report = await (0, checks_1.preflight)(dir);
741
+ expect(report).toMatchObject({
742
+ status: 'degraded',
743
+ mode: 'invalid',
744
+ reason: 'manifest-malformed',
745
+ projectRoot: dir,
746
+ manifestPath: path_1.default.join(dir, '.awm', 'sensors.json'),
747
+ remedy: expect.any(String),
748
+ });
749
+ expect(check(report, 'manifest').detail).not.toContain('no .awm/sensors.json');
750
+ expect(check(report, 'tools')).toBeUndefined();
751
+ });
721
752
  });
@@ -38,6 +38,29 @@ describe('sensor manifest contract', () => {
38
38
  expect((0, manifest_1.parseSensorManifest)(manifest, 'sensors.json')).toMatchObject({ kind: 'v2', pack: manifest });
39
39
  expect(JSON.parse((0, manifest_1.serializeManifestV2)(manifest))).toEqual(manifest);
40
40
  });
41
+ it('parses the three exact v3 modes without physical registry provenance', () => {
42
+ const project = {
43
+ schemaVersion: 3,
44
+ mode: 'project-sensors',
45
+ pack: 'js-ts',
46
+ source: { registry: 'baseline' },
47
+ packageRoot: 'cli',
48
+ sensors: validV2Manifest().sensors,
49
+ };
50
+ expect((0, manifest_1.parseSensorManifest)(project, 'sensors.json')).toMatchObject({
51
+ kind: 'v3',
52
+ pack: { mode: 'project-sensors', source: { registry: 'baseline' }, packageRoot: 'cli' },
53
+ });
54
+ expect((0, manifest_1.parseSensorManifest)({ schemaVersion: 3, mode: 'native-gate', reason: 'managed by platform' }, 'sensors.json')).toMatchObject({
55
+ kind: 'v3', pack: { mode: 'native-gate', reason: 'managed by platform' },
56
+ });
57
+ expect((0, manifest_1.parseSensorManifest)({ schemaVersion: 3, mode: 'opt-out', reason: 'not applicable' }, 'sensors.json')).toMatchObject({
58
+ kind: 'v3', pack: { mode: 'opt-out', reason: 'not applicable' },
59
+ });
60
+ expect(() => (0, manifest_1.parseSensorManifest)({ ...project, source: { registry: 'Baseline' } }, 'sensors.json')).toThrow('registry');
61
+ expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 3, mode: 'native-gate' }, 'sensors.json')).toThrow('reason');
62
+ expect(() => (0, manifest_1.parseSensorManifest)({ ...project, registryRoot: '/machine/root' }, 'sensors.json')).toThrow('unknown field');
63
+ });
41
64
  it('accepts and serializes a positive v2 project timeout (R3)', () => {
42
65
  const manifest = validV2Manifest();
43
66
  manifest.sensors.lint.timeout = 45_000;