agentic-workflow-manager 9.16.1 → 9.18.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 (54) hide show
  1. package/dist/src/commands/env/index.js +192 -0
  2. package/dist/src/commands/preflight/checks.js +63 -13
  3. package/dist/src/commands/registry/doctor.js +428 -0
  4. package/dist/src/commands/registry/index.js +52 -0
  5. package/dist/src/commands/registry/scaffold.js +140 -0
  6. package/dist/src/commands/sensors/bootstrap.js +2 -1
  7. package/dist/src/commands/sensors/compatibility/contract.js +30 -26
  8. package/dist/src/commands/sensors/compatibility/discovery.js +21 -0
  9. package/dist/src/commands/sensors/compatibility/manifest.js +169 -17
  10. package/dist/src/commands/sensors/compatibility/materialize.js +30 -5
  11. package/dist/src/commands/sensors/compatibility/source.js +3 -2
  12. package/dist/src/commands/sensors/coverage/render.js +8 -2
  13. package/dist/src/commands/sensors/coverage/resolve.js +7 -0
  14. package/dist/src/commands/sensors/init.js +15 -2
  15. package/dist/src/commands/sensors/migrate.js +13 -10
  16. package/dist/src/commands/sensors/prepare.js +53 -6
  17. package/dist/src/commands/sensors/project.js +60 -6
  18. package/dist/src/commands/sensors/result.js +18 -3
  19. package/dist/src/commands/sensors/run.js +124 -16
  20. package/dist/src/commands/sensors/status.js +163 -18
  21. package/dist/src/commands/watch/index.js +3 -0
  22. package/dist/src/core/environments/resolve.js +122 -0
  23. package/dist/src/core/environments/store.js +323 -0
  24. package/dist/src/core/facts/catalog.js +138 -0
  25. package/dist/src/core/facts/needs.js +57 -0
  26. package/dist/src/core/facts/types.js +21 -0
  27. package/dist/src/core/init/steps.js +5 -1
  28. package/dist/src/index.js +2 -0
  29. package/dist/tests/commands/env/index.test.js +261 -0
  30. package/dist/tests/commands/preflight/preflight.test.js +121 -0
  31. package/dist/tests/commands/registry/doctor.test.js +283 -0
  32. package/dist/tests/commands/registry/scaffold.test.js +186 -0
  33. package/dist/tests/commands/sensors/bootstrap.test.js +10 -0
  34. package/dist/tests/commands/sensors/compatibility/manifest.test.js +204 -0
  35. package/dist/tests/commands/sensors/init.test.js +107 -0
  36. package/dist/tests/commands/sensors/migrate.test.js +4 -3
  37. package/dist/tests/commands/sensors/partial-pack-bootstrap.test.js +1 -1
  38. package/dist/tests/commands/sensors/prepare.test.js +62 -0
  39. package/dist/tests/commands/sensors/result.test.js +66 -0
  40. package/dist/tests/commands/sensors/run.test.js +331 -0
  41. package/dist/tests/commands/sensors/status.test.js +204 -0
  42. package/dist/tests/commands/watch/reopened-196.test.js +9 -2
  43. package/dist/tests/core/environments/resolve.test.js +347 -0
  44. package/dist/tests/core/environments/store.test.js +248 -0
  45. package/dist/tests/core/facts/catalog.test.js +179 -0
  46. package/dist/tests/core/facts/needs.test.js +88 -0
  47. package/dist/tests/structural/native-release-artifacts.test.js +4 -2
  48. package/dist/tests/structural/operator-environments-docs-contract.test.js +70 -0
  49. package/dist/tests/structural/project-declared-sensors-docs-contract.test.js +104 -0
  50. package/package.json +1 -1
  51. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  52. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  53. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  54. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -0,0 +1,179 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const types_1 = require("../../../src/core/facts/types");
10
+ const catalog_1 = require("../../../src/core/facts/catalog");
11
+ const validRepos = {
12
+ schemaVersion: 1,
13
+ facts: {
14
+ 'repos.root': {
15
+ description: 'Local root for team clones',
16
+ valueType: 'path',
17
+ resolution: 'prompt',
18
+ durability: 'durable',
19
+ inject: 'reference',
20
+ },
21
+ },
22
+ };
23
+ describe('parseFactsCatalog (RF-1.1)', () => {
24
+ it('exports FACTS_MIN_CLI_VERSION as 9.18.0', () => {
25
+ expect(types_1.FACTS_MIN_CLI_VERSION).toBe('9.18.0');
26
+ });
27
+ it('parses a valid schemaVersion 1 catalog with path/prompt/durable/reference', () => {
28
+ const catalog = (0, catalog_1.parseFactsCatalog)(validRepos);
29
+ expect(catalog.schemaVersion).toBe(1);
30
+ expect(catalog.facts['repos.root']).toEqual({
31
+ description: 'Local root for team clones',
32
+ valueType: 'path',
33
+ resolution: 'prompt',
34
+ durability: 'durable',
35
+ inject: 'reference',
36
+ });
37
+ });
38
+ it('applies defaults durability durable, inject reference, resolution prompt', () => {
39
+ const catalog = (0, catalog_1.parseFactsCatalog)({
40
+ schemaVersion: 1,
41
+ facts: {
42
+ 'aws.profile': { valueType: 'string' },
43
+ },
44
+ });
45
+ expect(catalog.facts['aws.profile']).toMatchObject({
46
+ valueType: 'string',
47
+ resolution: 'prompt',
48
+ durability: 'durable',
49
+ inject: 'reference',
50
+ });
51
+ });
52
+ it('accepts empty facts object', () => {
53
+ expect((0, catalog_1.parseFactsCatalog)({ schemaVersion: 1, facts: {} }).facts).toEqual({});
54
+ });
55
+ it('rejects bad schemaVersion with FactsCatalogError', () => {
56
+ expect(() => (0, catalog_1.parseFactsCatalog)({ schemaVersion: 2, facts: {} })).toThrow(types_1.FactsCatalogError);
57
+ expect(() => (0, catalog_1.parseFactsCatalog)({ schemaVersion: 2, facts: {} })).toThrow(/schemaVersion/);
58
+ });
59
+ it('rejects missing facts object with FactsCatalogError', () => {
60
+ expect(() => (0, catalog_1.parseFactsCatalog)({ schemaVersion: 1 })).toThrow(types_1.FactsCatalogError);
61
+ expect(() => (0, catalog_1.parseFactsCatalog)({ schemaVersion: 1 })).toThrow(/facts/);
62
+ });
63
+ it('rejects unknown valueType with FactsCatalogError', () => {
64
+ expect(() => (0, catalog_1.parseFactsCatalog)({
65
+ schemaVersion: 1,
66
+ facts: { 'repos.root': { valueType: 'blob' } },
67
+ })).toThrow(types_1.FactsCatalogError);
68
+ expect(() => (0, catalog_1.parseFactsCatalog)({
69
+ schemaVersion: 1,
70
+ facts: { 'repos.root': { valueType: 'blob' } },
71
+ })).toThrow(/valueType/);
72
+ });
73
+ it('rejects resolution: command with message containing those words and v1 (RF-3.3 catalog)', () => {
74
+ expect(() => (0, catalog_1.parseFactsCatalog)({
75
+ schemaVersion: 1,
76
+ facts: {
77
+ 'repos.root': { valueType: 'path', resolution: 'command' },
78
+ },
79
+ })).toThrow(types_1.FactsCatalogError);
80
+ try {
81
+ (0, catalog_1.parseFactsCatalog)({
82
+ schemaVersion: 1,
83
+ facts: {
84
+ 'repos.root': { valueType: 'path', resolution: 'command' },
85
+ },
86
+ });
87
+ fail('expected throw');
88
+ }
89
+ catch (e) {
90
+ const msg = e.message;
91
+ expect(msg).toMatch(/resolution:\s*command/i);
92
+ expect(msg).toMatch(/v1/i);
93
+ }
94
+ });
95
+ it('rejects non-dotted fact ids', () => {
96
+ expect(() => (0, catalog_1.parseFactsCatalog)({
97
+ schemaVersion: 1,
98
+ facts: { root: { valueType: 'path' } },
99
+ })).toThrow(types_1.FactsCatalogError);
100
+ expect(() => (0, catalog_1.parseFactsCatalog)({
101
+ schemaVersion: 1,
102
+ facts: { 'Repos.Root': { valueType: 'path' } },
103
+ })).toThrow(types_1.FactsCatalogError);
104
+ });
105
+ it('parses from a file path', () => {
106
+ const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-facts-'));
107
+ try {
108
+ const file = path_1.default.join(dir, 'facts.json');
109
+ fs_1.default.writeFileSync(file, JSON.stringify(validRepos));
110
+ const catalog = (0, catalog_1.parseFactsCatalog)(file);
111
+ expect(catalog.facts['repos.root'].valueType).toBe('path');
112
+ }
113
+ finally {
114
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
115
+ }
116
+ });
117
+ it('rejects symlink facts.json (regular-file guard)', () => {
118
+ const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-facts-'));
119
+ try {
120
+ const real = path_1.default.join(dir, 'real-facts.json');
121
+ fs_1.default.writeFileSync(real, JSON.stringify(validRepos));
122
+ const link = path_1.default.join(dir, 'facts.json');
123
+ fs_1.default.symlinkSync(real, link);
124
+ expect(() => (0, catalog_1.parseFactsCatalog)(link)).toThrow(types_1.FactsCatalogError);
125
+ expect(() => (0, catalog_1.parseFactsCatalog)(link)).toThrow(/symbolic link|regular file/i);
126
+ }
127
+ finally {
128
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
129
+ }
130
+ });
131
+ it('requires enum values[] when valueType is enum', () => {
132
+ expect(() => (0, catalog_1.parseFactsCatalog)({
133
+ schemaVersion: 1,
134
+ facts: { 'env.tier': { valueType: 'enum' } },
135
+ })).toThrow(types_1.FactsCatalogError);
136
+ const ok = (0, catalog_1.parseFactsCatalog)({
137
+ schemaVersion: 1,
138
+ facts: { 'env.tier': { valueType: 'enum', values: ['dev', 'prod'] } },
139
+ });
140
+ expect(ok.facts['env.tier'].values).toEqual(['dev', 'prod']);
141
+ });
142
+ it('rejects volatile without finite ttl in (0, MAX] (RF-2.4 catalog)', () => {
143
+ expect(() => (0, catalog_1.parseFactsCatalog)({
144
+ schemaVersion: 1,
145
+ facts: { 'aws.token': { valueType: 'string', durability: 'volatile' } },
146
+ })).toThrow(types_1.FactsCatalogError);
147
+ expect(() => (0, catalog_1.parseFactsCatalog)({
148
+ schemaVersion: 1,
149
+ facts: { 'aws.token': { valueType: 'string', durability: 'volatile', ttl: 0 } },
150
+ })).toThrow(types_1.FactsCatalogError);
151
+ expect(() => (0, catalog_1.parseFactsCatalog)({
152
+ schemaVersion: 1,
153
+ facts: {
154
+ 'aws.token': {
155
+ valueType: 'string',
156
+ durability: 'volatile',
157
+ ttl: 365 * 24 * 3600 + 1,
158
+ },
159
+ },
160
+ })).toThrow(types_1.FactsCatalogError);
161
+ const ok = (0, catalog_1.parseFactsCatalog)({
162
+ schemaVersion: 1,
163
+ facts: { 'aws.token': { valueType: 'string', durability: 'volatile', ttl: 60 } },
164
+ });
165
+ expect(ok.facts['aws.token'].ttl).toBe(60);
166
+ });
167
+ it('rejects invalid JSON file with named FactsCatalogError (RF-1.1)', () => {
168
+ const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-facts-bad-'));
169
+ try {
170
+ const file = path_1.default.join(dir, 'facts.json');
171
+ fs_1.default.writeFileSync(file, '{not-json');
172
+ expect(() => (0, catalog_1.parseFactsCatalog)(file)).toThrow(types_1.FactsCatalogError);
173
+ expect(() => (0, catalog_1.parseFactsCatalog)(file)).toThrow(/Invalid JSON/i);
174
+ }
175
+ finally {
176
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
177
+ }
178
+ });
179
+ });
@@ -0,0 +1,88 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const catalog_1 = require("../../../src/core/facts/catalog");
10
+ const needs_1 = require("../../../src/core/facts/needs");
11
+ const types_1 = require("../../../src/core/facts/types");
12
+ describe('readSkillNeeds / findOrphanNeeds (RF-1.2, RF-1.3, RF-1.4)', () => {
13
+ let tmp;
14
+ beforeEach(() => {
15
+ tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-needs-'));
16
+ });
17
+ afterEach(() => {
18
+ fs_1.default.rmSync(tmp, { recursive: true, force: true });
19
+ });
20
+ function writeSkill(body) {
21
+ const dir = path_1.default.join(tmp, 'skills', 'demo');
22
+ fs_1.default.mkdirSync(dir, { recursive: true });
23
+ const skillPath = path_1.default.join(dir, 'SKILL.md');
24
+ fs_1.default.writeFileSync(skillPath, body);
25
+ return skillPath;
26
+ }
27
+ it('resolves needs: [repos.root] from frontmatter (RF-1.2)', () => {
28
+ const skillPath = writeSkill(`---
29
+ name: demo
30
+ description: d
31
+ needs:
32
+ - repos.root
33
+ ---
34
+ # Demo
35
+ `);
36
+ expect((0, needs_1.readSkillNeeds)(skillPath)).toEqual(['repos.root']);
37
+ });
38
+ it('accepts flow-style needs list', () => {
39
+ const skillPath = writeSkill(`---
40
+ name: demo
41
+ description: d
42
+ needs: [repos.root, aws.profile.dev]
43
+ ---
44
+ # Demo
45
+ `);
46
+ expect((0, needs_1.readSkillNeeds)(skillPath)).toEqual(['repos.root', 'aws.profile.dev']);
47
+ });
48
+ it('returns empty when needs omitted (RF-1.4)', () => {
49
+ const skillPath = writeSkill(`---
50
+ name: demo
51
+ description: d
52
+ ---
53
+ # Demo
54
+ `);
55
+ expect((0, needs_1.readSkillNeeds)(skillPath)).toEqual([]);
56
+ });
57
+ it('throws SkillNeedsError for invalid needs shape', () => {
58
+ const skillPath = writeSkill(`---
59
+ name: demo
60
+ description: d
61
+ needs: repos.root
62
+ ---
63
+ # Demo
64
+ `);
65
+ expect(() => (0, needs_1.readSkillNeeds)(skillPath)).toThrow(types_1.SkillNeedsError);
66
+ });
67
+ it('lists unknown ids as orphans without inventing facts (RF-1.3)', () => {
68
+ const catalog = (0, catalog_1.parseFactsCatalog)({
69
+ schemaVersion: 1,
70
+ facts: { 'repos.root': { valueType: 'path' } },
71
+ });
72
+ expect((0, needs_1.findOrphanNeeds)(['repos.root', 'ghost.fact'], catalog)).toEqual(['ghost.fact']);
73
+ expect((0, needs_1.findOrphanNeeds)(['repos.root'], catalog)).toEqual([]);
74
+ expect((0, needs_1.findOrphanNeeds)([], catalog)).toEqual([]);
75
+ });
76
+ it('treats Object.prototype keys as orphans (not catalog facts)', () => {
77
+ const catalog = (0, catalog_1.parseFactsCatalog)({
78
+ schemaVersion: 1,
79
+ facts: { 'repos.root': { valueType: 'path' } },
80
+ });
81
+ // `in` would match prototype; Object.hasOwn must not.
82
+ expect((0, needs_1.findOrphanNeeds)(['toString'], catalog)).toEqual(['toString']);
83
+ expect((0, needs_1.findOrphanNeeds)(['__proto__', 'constructor'], catalog)).toEqual([
84
+ '__proto__',
85
+ 'constructor',
86
+ ]);
87
+ });
88
+ });
@@ -17,8 +17,10 @@ function assertArmPartition(source) {
17
17
  const [matrixJob, rest] = workflow.split(/^ windows-arm-hot:\s*$/m);
18
18
  expect(rest).toBeDefined();
19
19
  const hotJob = rest.split(/^ [a-z][\w-]*:\s*$/m)[0];
20
- expect(matrixJob).toMatch(/- name: Tests?\n if: matrix\.target != 'win32-arm64'\n working-directory: cli\n run: npx jest --runInBand --bail\n/);
21
- const remainder = matrixJob.match(/- name: Tests? \(Windows ARM remainder\)\n if: matrix\.target == 'win32-arm64'\n working-directory: cli\n run: ([^\n]+)/)?.[1];
20
+ // Optional env: (e.g. AWM_BASELINE_REGISTRY_ROOT for RF-5.1 docs contract) may
21
+ // sit between working-directory and run; release.yml still omits it.
22
+ expect(matrixJob).toMatch(/- name: Tests?\n if: matrix\.target != 'win32-arm64'\n working-directory: cli\n(?: env:\n(?: [^\n]+\n)+)? run: npx jest --runInBand --bail\n/);
23
+ const remainder = matrixJob.match(/- name: Tests? \(Windows ARM remainder\)\n if: matrix\.target == 'win32-arm64'\n working-directory: cli\n(?: env:\n(?: [^\n]+\n)+)? run: ([^\n]+)/)?.[1];
22
24
  expect(remainder).toMatch(/^npx jest --runInBand --bail --testPathIgnorePatterns='[^']+'$/);
23
25
  const ignorePattern = remainder?.match(/--testPathIgnorePatterns='([^']+)'/)?.[1];
24
26
  expect(ignorePattern).toBe(String.raw `node_modules|[/\\]cli[/\\]tests[/\\]commands[/\\]watch[/\\]track-(finalize|freeze)\.test\.ts$`);
@@ -0,0 +1,70 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ /**
9
+ * CMD-SCAFFOLD-DOCS — RF-5.2 operator-environments documentation contract.
10
+ * Asserts runbook + cli-reference document scaffold/doctor/env and state that
11
+ * awm-cenco-infra-registry migration is out of scope (RF-5.2).
12
+ */
13
+ const repoRoot = path_1.default.resolve(__dirname, '../../..');
14
+ const runbookPath = path_1.default.join(repoRoot, 'docs', 'runbook.md');
15
+ const cliReferencePath = path_1.default.join(repoRoot, 'docs', 'cli-reference.md');
16
+ const designPath = 'docs/plans/2026-10-12-operator-environments-design.md';
17
+ function teamRegistriesSection(runbook) {
18
+ const start = runbook.indexOf('## Team registries');
19
+ expect(start).toBeGreaterThanOrEqual(0);
20
+ const rest = runbook.slice(start);
21
+ const nextH2 = rest.search(/\n## (?!Team)/);
22
+ return nextH2 === -1 ? rest : rest.slice(0, nextH2);
23
+ }
24
+ function registriesSection(cliRef) {
25
+ const start = cliRef.indexOf('## Registries & pinning');
26
+ expect(start).toBeGreaterThanOrEqual(0);
27
+ const rest = cliRef.slice(start);
28
+ const nextH2 = rest.search(/\n## (?!Registries)/);
29
+ return nextH2 === -1 ? rest : rest.slice(0, nextH2);
30
+ }
31
+ describe('operator-environments docs contract (RF-5.2)', () => {
32
+ it('runbook team-registries leads with scaffold and mentions doctor + design RF-5.2 out-of-scope', () => {
33
+ const runbook = fs_1.default.readFileSync(runbookPath, 'utf-8');
34
+ const section = teamRegistriesSection(runbook);
35
+ const scaffoldIdx = section.indexOf('awm registry scaffold');
36
+ const addIdx = section.indexOf('awm registry add');
37
+ expect(scaffoldIdx).toBeGreaterThanOrEqual(0);
38
+ expect(addIdx).toBeGreaterThan(scaffoldIdx);
39
+ expect(section).toContain('awm registry doctor');
40
+ // RF-5.2: Cenco infra registry is out of scope — require the out-of-scope
41
+ // phrase near the cenco mention (same paragraph/window), not a loose
42
+ // match elsewhere in the section.
43
+ expect(section).toMatch(/RF-5\.2/);
44
+ expect(section).toContain(designPath);
45
+ const cencoIdx = section.search(/awm-cenco-infra-registry/);
46
+ expect(cencoIdx).toBeGreaterThanOrEqual(0);
47
+ const windowStart = Math.max(0, cencoIdx - 120);
48
+ const windowEnd = Math.min(section.length, cencoIdx + 160);
49
+ const nearCenco = section.slice(windowStart, windowEnd);
50
+ expect(nearCenco).toMatch(/out of scope|out-of-scope|does not modify|SHALL NOT modify/i);
51
+ });
52
+ it('cli-reference documents registry scaffold, registry doctor, and awm env', () => {
53
+ const cliRef = fs_1.default.readFileSync(cliReferencePath, 'utf-8');
54
+ const registries = registriesSection(cliRef);
55
+ expect(registries).toMatch(/###\s+`awm registry scaffold/);
56
+ expect(registries).toMatch(/###\s+`awm registry doctor/);
57
+ // env lives as its own H2; require the three command strings in the file
58
+ // with scoped checks so headings cannot be satisfied by a stray mention.
59
+ expect(cliRef).toContain('awm registry scaffold');
60
+ expect(cliRef).toContain('awm registry doctor');
61
+ const envStart = cliRef.search(/^##\s+.*\benv\b/im);
62
+ expect(envStart).toBeGreaterThanOrEqual(0);
63
+ const envRest = cliRef.slice(envStart);
64
+ const envNext = envRest.search(/\n## /);
65
+ const envSection = envNext === -1 ? envRest : envRest.slice(0, envNext);
66
+ expect(envSection).toMatch(/awm env/);
67
+ expect(envSection).toMatch(/env set|`awm env set`/i);
68
+ expect(envSection).toMatch(/env status|`awm env status`/i);
69
+ });
70
+ });
@@ -0,0 +1,104 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ /**
9
+ * CMD-DOCS — RF-5.1 / RF-5.2 (project-declared sensors documentation).
10
+ *
11
+ * Resolves awm-baseline-registry from (in order):
12
+ * 1. `AWM_BASELINE_REGISTRY_ROOT` (CI docs checkout)
13
+ * 2. nested `repoRoot/awm-baseline-registry` (CI support-matrix layout)
14
+ * 3. sibling `repoRoot/../awm-baseline-registry` (maintainer workspace)
15
+ *
16
+ * Prefer a checkout that already contains RF-5.1 (`## Project sensors vs packs`).
17
+ * Missing / too-old checkouts fail loudly (no silent skip).
18
+ */
19
+ const repoRoot = path_1.default.resolve(__dirname, '../../..');
20
+ const cliReferencePath = path_1.default.join(repoRoot, 'docs', 'cli-reference.md');
21
+ const RF51_HEADING = '## Project sensors vs packs';
22
+ function setupSensorsSkillPath(registryRoot) {
23
+ return path_1.default.join(registryRoot, 'skills', 'setup-sensors', 'SKILL.md');
24
+ }
25
+ function resolveSetupSensorsPath() {
26
+ const envRoot = process.env.AWM_BASELINE_REGISTRY_ROOT?.trim();
27
+ // When CI/maintainer sets the override, that root is authoritative (no silent
28
+ // fallback to a nested immutable pin that lacks RF-5.1 prose).
29
+ const candidates = envRoot
30
+ ? [path_1.default.resolve(envRoot)]
31
+ : [
32
+ path_1.default.join(repoRoot, 'awm-baseline-registry'),
33
+ path_1.default.join(repoRoot, '..', 'awm-baseline-registry'),
34
+ ];
35
+ const existing = candidates
36
+ .map(root => ({ root, skill: setupSensorsSkillPath(root) }))
37
+ .filter(entry => fs_1.default.existsSync(entry.skill));
38
+ if (existing.length === 0) {
39
+ throw new Error('Baseline registry checkout missing for RF-5.1 docs contract. Tried:\n'
40
+ + candidates.map(root => ` - ${setupSensorsSkillPath(root)}`).join('\n')
41
+ + '\nClone awm-baseline-registry as a sibling, nest it under the CLI repo, '
42
+ + 'or set AWM_BASELINE_REGISTRY_ROOT to a checkout that includes setup-sensors RF-5.1.');
43
+ }
44
+ const withRf51 = existing.find(entry => fs_1.default.readFileSync(entry.skill, 'utf8').includes(RF51_HEADING));
45
+ if (!withRf51) {
46
+ throw new Error('Baseline registry checkout is present but too old for RF-5.1 '
47
+ + `(missing ${RF51_HEADING}). Checked:\n`
48
+ + existing.map(entry => ` - ${entry.skill}`).join('\n')
49
+ + '\nPoint AWM_BASELINE_REGISTRY_ROOT at a registry tip that documents project-declared sensors '
50
+ + '(coordinated awm-baseline-registry PR), without replacing the immutable v2.0.1 support-matrix pin.');
51
+ }
52
+ return withRf51.skill;
53
+ }
54
+ function sensorsSection(text) {
55
+ const start = text.indexOf('## Sensors (per-project computational checks)');
56
+ expect(start).toBeGreaterThanOrEqual(0);
57
+ const rest = text.slice(start);
58
+ const nextH2 = rest.search(/\n## (?!Sensors)/);
59
+ return nextH2 === -1 ? rest : rest.slice(0, nextH2);
60
+ }
61
+ /** Skill body after YAML frontmatter (content after the closing `---`). */
62
+ function skillBody(skill) {
63
+ const match = skill.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n([\s\S]*)$/);
64
+ if (!match) {
65
+ throw new Error('setup-sensors SKILL.md missing YAML frontmatter delimiters');
66
+ }
67
+ return match[1];
68
+ }
69
+ /** Named H2 section inside the skill body; fails if the heading is absent. */
70
+ function skillSection(body, heading) {
71
+ const start = body.indexOf(heading);
72
+ expect(start).toBeGreaterThanOrEqual(0);
73
+ const rest = body.slice(start);
74
+ const nextH2 = rest.search(/\n## /);
75
+ return nextH2 === -1 ? rest : rest.slice(0, nextH2);
76
+ }
77
+ describe('project-declared sensors docs contract (CMD-DOCS)', () => {
78
+ it('cli-reference sensors section documents project markers, provenance, exit-code, and non-gating (RF-5.2)', () => {
79
+ const text = fs_1.default.readFileSync(cliReferencePath, 'utf8');
80
+ const section = sensorsSection(text);
81
+ expect(section).toMatch(/source:\s*"project"|source"\s*:\s*"project"/);
82
+ expect(section).toContain('project-declared');
83
+ expect(section).toContain('exit-code');
84
+ expect(section).toMatch(/provenance[\s\S]{0,220}(?:does not|never)\s+(?:block|blocks)[\s\S]{0,100}(?:unattended|gates)/i);
85
+ // Optional pack (omit or null) — RF-5.2 clause already in cli-reference prose.
86
+ expect(section).toMatch(/[Pp]ack may be omitted or set\s+to\s+`?null`?[\s\S]{0,120}project-declared/i);
87
+ // JSON-vs-schema diagnostics distinction — RF-5.2.
88
+ expect(section).toMatch(/not valid JSON[\s\S]{0,220}schema-invalid/i);
89
+ });
90
+ it('setup-sensors skill documents project vs pack, JSON example, and non-gating provenance (RF-5.1)', () => {
91
+ const setupSensorsPath = resolveSetupSensorsPath();
92
+ const skill = fs_1.default.readFileSync(setupSensorsPath, 'utf8');
93
+ const body = skillBody(skill);
94
+ // Scope when-to-use / project-vs-packs (and related RF-5.1 prose) to the
95
+ // dedicated body section — frontmatter description must not satisfy these.
96
+ const projectVsPacks = skillSection(body, RF51_HEADING);
97
+ // Both guidance clauses are required — Prefer|Declare OR let either alone satisfy.
98
+ expect(projectVsPacks).toMatch(/Prefer a \*\*pack\*\*/);
99
+ expect(projectVsPacks).toMatch(/Declare a \*\*project sensor\*\*\s+when\s+the check is project-owned/i);
100
+ expect(projectVsPacks).toMatch(/"source"\s*:\s*"project"/);
101
+ expect(projectVsPacks).toMatch(/```json[\s\S]*?"source"\s*:\s*"project"[\s\S]*?```/);
102
+ expect(projectVsPacks).toMatch(/project-declared[\s\S]{0,220}(?:does not|never)\s+(?:block|blocks)[\s\S]{0,100}unattended/i);
103
+ });
104
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.16.1",
3
+ "version": "9.18.0",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
Binary file
Binary file