agentic-workflow-manager 9.20.4 → 9.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/commands/change/index.js +14 -3
- package/dist/src/commands/preflight/checks.js +31 -0
- package/dist/src/commands/sensors/compatibility/contract.js +56 -3
- package/dist/src/commands/sensors/compatibility/manifest.js +16 -2
- package/dist/src/commands/sensors/index.js +10 -1
- package/dist/src/commands/sensors/init.js +7 -1
- package/dist/src/commands/sensors/inputs-digest.js +315 -0
- package/dist/src/commands/sensors/prepare.js +10 -0
- package/dist/src/commands/sensors/result-cache.js +278 -0
- package/dist/src/commands/sensors/result.js +31 -12
- package/dist/src/commands/sensors/run.js +265 -8
- package/dist/src/commands/sensors/status.js +66 -7
- package/dist/src/commands/watch/init.js +3 -15
- package/dist/src/core/awm-gitignore.js +211 -0
- package/dist/src/core/changes/receipt.js +219 -9
- package/dist/src/core/diagnostics/checks.js +11 -0
- package/dist/tests/commands/change/new.test.js +1 -1
- package/dist/tests/commands/change/validate-unmeasured.test.js +246 -0
- package/dist/tests/commands/preflight/awm-gitignore.test.js +52 -0
- package/dist/tests/commands/preflight/sensors-inputs.test.js +110 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +60 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +94 -0
- package/dist/tests/commands/sensors/init-gitignore.test.js +59 -0
- package/dist/tests/commands/sensors/inputs-digest.test.js +373 -0
- package/dist/tests/commands/sensors/result-cache.test.js +352 -0
- package/dist/tests/commands/sensors/run-cache.test.js +434 -0
- package/dist/tests/commands/sensors/run-slice.test.js +171 -0
- package/dist/tests/commands/sensors/status-inputs.test.js +271 -0
- package/dist/tests/commands/watch/init-gitignore.test.js +34 -0
- package/dist/tests/commands/watch/track-freeze.test.js +11 -7
- package/dist/tests/commands/watch/watch-init.test.js +3 -1
- package/dist/tests/core/awm-gitignore.test.js +58 -0
- package/dist/tests/core/changes/receipt.test.js +261 -2
- package/dist/tests/core/diagnostics/awm-gitignore.test.js +101 -0
- package/dist/tests/core/ledger/gitignore.test.js +6 -1
- package/dist/tests/structural/sensor-execution-optimization-docs-contract.test.js +102 -0
- package/dist/tests/structural/sensor-slice-decision-contract.test.js +63 -0
- package/package.json +2 -1
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -141,7 +141,7 @@ describe('receipt', () => {
|
|
|
141
141
|
test('R2.3 the receipt carries schema, commit, ranAt, cliVersion and overall from the run', () => {
|
|
142
142
|
const head = g('rev-parse', 'HEAD');
|
|
143
143
|
const r = (0, receipt_1.buildReceipt)({ commit: head, ranAt: '2026-10-17T10:00:00.000Z', cliVersion: '9.20.0', output: { overall: 'fail', sensors: [{ name: 'lint', status: 'fail', errors: [] }] } });
|
|
144
|
-
expect(r).toMatchObject({ schema:
|
|
144
|
+
expect(r).toMatchObject({ schema: 2, commit: head, ranAt: '2026-10-17T10:00:00.000Z', cliVersion: '9.20.0', overall: 'fail', sensors: { lint: 'fail' } });
|
|
145
145
|
});
|
|
146
146
|
(0, helpers_1.posixOnly)('R2.1 a receipt replacing an existing 0666 file ends with mode 0600', () => {
|
|
147
147
|
const head = g('rev-parse', 'HEAD');
|
|
@@ -646,7 +646,7 @@ describe('receipt', () => {
|
|
|
646
646
|
['unknown sensor status', (r) => { r.sensors.lint = 'ok'; }],
|
|
647
647
|
['array sensors', (r) => { r.sensors = []; }],
|
|
648
648
|
['null sensors', (r) => { r.sensors = null; }],
|
|
649
|
-
['schema
|
|
649
|
+
['schema 3', (r) => { r.schema = 3; }],
|
|
650
650
|
['non-string cliVersion', (r) => { r.cliVersion = 1; }],
|
|
651
651
|
['multi-line cliVersion', (r) => { r.cliVersion = '1.0\n2.0'; }],
|
|
652
652
|
['multi-line sensor name', (r) => { r.sensors['bad\nname'] = 'pass'; }],
|
|
@@ -944,4 +944,263 @@ describe('sensors run wiring (R2.2, R2.4)', () => {
|
|
|
944
944
|
expect(fs_1.default.existsSync(receiptsDir())).toBe(false);
|
|
945
945
|
});
|
|
946
946
|
});
|
|
947
|
+
test('RF-4.2/4.4: all-cached clean full gate still writes a HEAD receipt (digest is authority)', async () => {
|
|
948
|
+
run_1.runSensors.mockResolvedValue({
|
|
949
|
+
overall: 'pass',
|
|
950
|
+
sensors: [
|
|
951
|
+
{
|
|
952
|
+
name: 'lint',
|
|
953
|
+
status: 'pass',
|
|
954
|
+
errors: [],
|
|
955
|
+
cached: true,
|
|
956
|
+
cacheProvenance: {
|
|
957
|
+
originCommit: 'c'.repeat(40),
|
|
958
|
+
originDirty: false,
|
|
959
|
+
inputDigest: `sha256:${'d'.repeat(64)}`,
|
|
960
|
+
sensorFingerprint: `sha256:${'e'.repeat(64)}`,
|
|
961
|
+
},
|
|
962
|
+
measurement: {
|
|
963
|
+
inputs: ['src/**/*.ts'],
|
|
964
|
+
packageRootRel: '',
|
|
965
|
+
inputDigest: `sha256:${'d'.repeat(64)}`,
|
|
966
|
+
sensorFingerprint: `sha256:${'e'.repeat(64)}`,
|
|
967
|
+
measuredPaths: ['a.txt', '.awm/sensors.json'],
|
|
968
|
+
},
|
|
969
|
+
},
|
|
970
|
+
{
|
|
971
|
+
name: 'a-typecheck',
|
|
972
|
+
status: 'pass',
|
|
973
|
+
errors: [],
|
|
974
|
+
cached: true,
|
|
975
|
+
cacheProvenance: {
|
|
976
|
+
inputDigest: `sha256:${'f'.repeat(64)}`,
|
|
977
|
+
sensorFingerprint: `sha256:${'e'.repeat(64)}`,
|
|
978
|
+
},
|
|
979
|
+
measurement: {
|
|
980
|
+
inputs: ['src/**/*.ts'],
|
|
981
|
+
packageRootRel: '',
|
|
982
|
+
inputDigest: `sha256:${'f'.repeat(64)}`,
|
|
983
|
+
sensorFingerprint: `sha256:${'e'.repeat(64)}`,
|
|
984
|
+
measuredPaths: ['a.txt', '.awm/sensors.json'],
|
|
985
|
+
},
|
|
986
|
+
},
|
|
987
|
+
],
|
|
988
|
+
manifestPath: rootManifest(),
|
|
989
|
+
});
|
|
990
|
+
await runCli();
|
|
991
|
+
expect(written(stderr)).toBe('');
|
|
992
|
+
expect(process.exitCode).toBe(0);
|
|
993
|
+
const head = g('rev-parse', 'HEAD');
|
|
994
|
+
const read = (0, receipt_1.readReceipt)(root, head);
|
|
995
|
+
expect(read.ok).toBe(true);
|
|
996
|
+
if (!read.ok)
|
|
997
|
+
return;
|
|
998
|
+
expect(read.receipt.schema).toBe(2);
|
|
999
|
+
expect(read.receipt.sensors).toEqual({ 'a-typecheck': 'pass', lint: 'pass' });
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
describe('RF-4.3 receipt schema 2', () => {
|
|
1003
|
+
test('buildReceipt writes schema 2 with measurements and alwaysMeasuredPaths including baseline', () => {
|
|
1004
|
+
const head = g('rev-parse', 'HEAD');
|
|
1005
|
+
const r = (0, receipt_1.buildReceipt)({
|
|
1006
|
+
commit: head,
|
|
1007
|
+
ranAt: '2026-10-17T10:00:00.000Z',
|
|
1008
|
+
cliVersion: '9.21.0',
|
|
1009
|
+
output: {
|
|
1010
|
+
overall: 'pass',
|
|
1011
|
+
sensors: [{
|
|
1012
|
+
name: 'lint',
|
|
1013
|
+
status: 'pass',
|
|
1014
|
+
errors: [],
|
|
1015
|
+
measurement: {
|
|
1016
|
+
inputs: ['src/**/*.ts'],
|
|
1017
|
+
packageRootRel: '',
|
|
1018
|
+
inputDigest: `sha256:${'1'.repeat(64)}`,
|
|
1019
|
+
sensorFingerprint: `sha256:${'2'.repeat(64)}`,
|
|
1020
|
+
measuredPaths: ['src/a.ts', '.awm/sensors.json', 'package-lock.json'],
|
|
1021
|
+
},
|
|
1022
|
+
}],
|
|
1023
|
+
},
|
|
1024
|
+
alwaysMeasuredPaths: ['.awm/sensors.json', '.awm/sensors.baseline.json', 'package-lock.json'],
|
|
1025
|
+
});
|
|
1026
|
+
expect(r.schema).toBe(2);
|
|
1027
|
+
expect(r.sensors).toEqual({ lint: 'pass' });
|
|
1028
|
+
const v2 = r;
|
|
1029
|
+
expect(v2.measurements).toEqual({
|
|
1030
|
+
lint: {
|
|
1031
|
+
inputs: ['src/**/*.ts'],
|
|
1032
|
+
packageRootRel: '',
|
|
1033
|
+
inputDigest: `sha256:${'1'.repeat(64)}`,
|
|
1034
|
+
sensorFingerprint: `sha256:${'2'.repeat(64)}`,
|
|
1035
|
+
measuredPaths: ['.awm/sensors.json', 'package-lock.json', 'src/a.ts'],
|
|
1036
|
+
},
|
|
1037
|
+
});
|
|
1038
|
+
expect(v2.alwaysMeasuredPaths).toEqual(['.awm/sensors.baseline.json', '.awm/sensors.json', 'package-lock.json']);
|
|
1039
|
+
expect(v2.digest).toMatch(/^sha256:[0-9a-f]{64}$/);
|
|
1040
|
+
expect((0, receipt_1.readReceipt)(root, (() => { (0, receipt_1.writeReceipt)(root, r); return head; })()).ok).toBe(true);
|
|
1041
|
+
});
|
|
1042
|
+
test('schema 1 receipts remain readable', () => {
|
|
1043
|
+
const head = g('rev-parse', 'HEAD');
|
|
1044
|
+
const body = {
|
|
1045
|
+
schema: 1,
|
|
1046
|
+
commit: head,
|
|
1047
|
+
ranAt: '2026-10-17T10:00:00.000Z',
|
|
1048
|
+
cliVersion: '9.20.0',
|
|
1049
|
+
overall: 'pass',
|
|
1050
|
+
sensors: { lint: 'pass' },
|
|
1051
|
+
};
|
|
1052
|
+
const receipt = { ...body, digest: (0, receipt_1.computeReceiptDigest)(body) };
|
|
1053
|
+
(0, receipt_1.writeReceipt)(root, receipt);
|
|
1054
|
+
const read = (0, receipt_1.readReceipt)(root, head);
|
|
1055
|
+
expect(read).toEqual({ ok: true, receipt });
|
|
1056
|
+
});
|
|
1057
|
+
test('schema 2 digest covers measurements and alwaysMeasuredPaths', () => {
|
|
1058
|
+
const base = {
|
|
1059
|
+
schema: 2,
|
|
1060
|
+
commit: 'a'.repeat(40),
|
|
1061
|
+
ranAt: '2026-10-17T10:00:00.000Z',
|
|
1062
|
+
cliVersion: '1.0.0',
|
|
1063
|
+
overall: 'pass',
|
|
1064
|
+
sensors: { lint: 'pass' },
|
|
1065
|
+
measurements: {
|
|
1066
|
+
lint: {
|
|
1067
|
+
inputs: ['src/**'],
|
|
1068
|
+
packageRootRel: '',
|
|
1069
|
+
inputDigest: `sha256:${'1'.repeat(64)}`,
|
|
1070
|
+
sensorFingerprint: `sha256:${'2'.repeat(64)}`,
|
|
1071
|
+
measuredPaths: ['src/a.ts'],
|
|
1072
|
+
},
|
|
1073
|
+
},
|
|
1074
|
+
alwaysMeasuredPaths: ['.awm/sensors.baseline.json'],
|
|
1075
|
+
};
|
|
1076
|
+
const d = (0, receipt_1.computeReceiptDigest)(base);
|
|
1077
|
+
const measuredChanged = {
|
|
1078
|
+
...base,
|
|
1079
|
+
measurements: {
|
|
1080
|
+
lint: { ...base.measurements.lint, measuredPaths: ['src/b.ts'] },
|
|
1081
|
+
},
|
|
1082
|
+
};
|
|
1083
|
+
const alwaysChanged = {
|
|
1084
|
+
...base,
|
|
1085
|
+
alwaysMeasuredPaths: ['.awm/sensors.json'],
|
|
1086
|
+
};
|
|
1087
|
+
expect((0, receipt_1.computeReceiptDigest)(measuredChanged)).not.toBe(d);
|
|
1088
|
+
expect((0, receipt_1.computeReceiptDigest)(alwaysChanged)).not.toBe(d);
|
|
1089
|
+
});
|
|
1090
|
+
test('RF-4.6 helper: relaxation only when every recorded sensor has authored inputs', () => {
|
|
1091
|
+
expect((0, receipt_1.allowsUnmeasuredRelaxation)({
|
|
1092
|
+
schema: 2,
|
|
1093
|
+
commit: 'a'.repeat(40),
|
|
1094
|
+
ranAt: '2026-10-17T10:00:00.000Z',
|
|
1095
|
+
cliVersion: '1',
|
|
1096
|
+
overall: 'pass',
|
|
1097
|
+
sensors: { lint: 'pass', test: 'pass' },
|
|
1098
|
+
measurements: {
|
|
1099
|
+
lint: {
|
|
1100
|
+
inputs: ['src/**'],
|
|
1101
|
+
packageRootRel: '',
|
|
1102
|
+
inputDigest: `sha256:${'1'.repeat(64)}`,
|
|
1103
|
+
sensorFingerprint: `sha256:${'2'.repeat(64)}`,
|
|
1104
|
+
measuredPaths: ['src/a.ts'],
|
|
1105
|
+
},
|
|
1106
|
+
},
|
|
1107
|
+
alwaysMeasuredPaths: [],
|
|
1108
|
+
digest: 'sha256:' + '0'.repeat(64),
|
|
1109
|
+
})).toBe(false);
|
|
1110
|
+
expect((0, receipt_1.allowsUnmeasuredRelaxation)({
|
|
1111
|
+
schema: 2,
|
|
1112
|
+
commit: 'a'.repeat(40),
|
|
1113
|
+
ranAt: '2026-10-17T10:00:00.000Z',
|
|
1114
|
+
cliVersion: '1',
|
|
1115
|
+
overall: 'pass',
|
|
1116
|
+
sensors: { lint: 'pass' },
|
|
1117
|
+
measurements: {
|
|
1118
|
+
lint: {
|
|
1119
|
+
inputs: ['src/**'],
|
|
1120
|
+
packageRootRel: '',
|
|
1121
|
+
inputDigest: `sha256:${'1'.repeat(64)}`,
|
|
1122
|
+
sensorFingerprint: `sha256:${'2'.repeat(64)}`,
|
|
1123
|
+
measuredPaths: ['src/a.ts'],
|
|
1124
|
+
},
|
|
1125
|
+
},
|
|
1126
|
+
alwaysMeasuredPaths: ['.awm/sensors.baseline.json'],
|
|
1127
|
+
digest: 'sha256:' + '0'.repeat(64),
|
|
1128
|
+
})).toBe(true);
|
|
1129
|
+
expect((0, receipt_1.allowsUnmeasuredRelaxation)({
|
|
1130
|
+
schema: 1,
|
|
1131
|
+
commit: 'a'.repeat(40),
|
|
1132
|
+
ranAt: '2026-10-17T10:00:00.000Z',
|
|
1133
|
+
cliVersion: '1',
|
|
1134
|
+
overall: 'pass',
|
|
1135
|
+
sensors: { lint: 'pass' },
|
|
1136
|
+
digest: 'sha256:' + '0'.repeat(64),
|
|
1137
|
+
})).toBe(false);
|
|
1138
|
+
});
|
|
1139
|
+
test('readReceipt rejects schema-2 measurements with unsorted measuredPaths or malformed measurement shapes', () => {
|
|
1140
|
+
const head = g('rev-parse', 'HEAD');
|
|
1141
|
+
const baseMeasurement = {
|
|
1142
|
+
inputs: ['src/**/*.ts'],
|
|
1143
|
+
packageRootRel: '',
|
|
1144
|
+
inputDigest: `sha256:${'1'.repeat(64)}`,
|
|
1145
|
+
sensorFingerprint: `sha256:${'2'.repeat(64)}`,
|
|
1146
|
+
measuredPaths: ['a.ts', 'b.ts'],
|
|
1147
|
+
};
|
|
1148
|
+
const writeV2 = (measurements, alwaysMeasuredPaths = ['.awm/sensors.baseline.json']) => {
|
|
1149
|
+
const body = {
|
|
1150
|
+
schema: 2,
|
|
1151
|
+
commit: head,
|
|
1152
|
+
ranAt: '2026-10-17T10:00:00.000Z',
|
|
1153
|
+
cliVersion: '9.21.0',
|
|
1154
|
+
overall: 'pass',
|
|
1155
|
+
sensors: { lint: 'pass' },
|
|
1156
|
+
measurements,
|
|
1157
|
+
alwaysMeasuredPaths,
|
|
1158
|
+
};
|
|
1159
|
+
const receipt = { ...body, digest: (0, receipt_1.computeReceiptDigest)(body) };
|
|
1160
|
+
const dir = path_1.default.join(root, '.awm', 'sensors', 'runs');
|
|
1161
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
1162
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, `${head}.json`), JSON.stringify(receipt) + '\n');
|
|
1163
|
+
};
|
|
1164
|
+
writeV2({ lint: { ...baseMeasurement, measuredPaths: ['b.ts', 'a.ts'] } });
|
|
1165
|
+
expect((0, receipt_1.readReceipt)(root, head)).toEqual({ ok: false, code: 'receipt-invalid' });
|
|
1166
|
+
writeV2({ lint: { ...baseMeasurement, measuredPaths: 'a.ts' } });
|
|
1167
|
+
expect((0, receipt_1.readReceipt)(root, head)).toEqual({ ok: false, code: 'receipt-invalid' });
|
|
1168
|
+
writeV2({ lint: { ...baseMeasurement, inputs: [] } });
|
|
1169
|
+
expect((0, receipt_1.readReceipt)(root, head)).toEqual({ ok: false, code: 'receipt-invalid' });
|
|
1170
|
+
writeV2({ lint: { ...baseMeasurement, packageRootRel: '../escape' } });
|
|
1171
|
+
expect((0, receipt_1.readReceipt)(root, head)).toEqual({ ok: false, code: 'receipt-invalid' });
|
|
1172
|
+
});
|
|
1173
|
+
test('measurement.inputs on schema-2 parse reuse RF-1.5 glob rules (reject .., !, braces, extglobs)', () => {
|
|
1174
|
+
const head = g('rev-parse', 'HEAD');
|
|
1175
|
+
const mk = (inputs) => {
|
|
1176
|
+
const body = {
|
|
1177
|
+
schema: 2,
|
|
1178
|
+
commit: head,
|
|
1179
|
+
ranAt: '2026-10-17T10:00:00.000Z',
|
|
1180
|
+
cliVersion: '9.21.0',
|
|
1181
|
+
overall: 'pass',
|
|
1182
|
+
sensors: { lint: 'pass' },
|
|
1183
|
+
measurements: {
|
|
1184
|
+
lint: {
|
|
1185
|
+
inputs,
|
|
1186
|
+
packageRootRel: '',
|
|
1187
|
+
inputDigest: `sha256:${'1'.repeat(64)}`,
|
|
1188
|
+
sensorFingerprint: `sha256:${'2'.repeat(64)}`,
|
|
1189
|
+
measuredPaths: ['src/a.ts'],
|
|
1190
|
+
},
|
|
1191
|
+
},
|
|
1192
|
+
alwaysMeasuredPaths: ['.awm/sensors.baseline.json'],
|
|
1193
|
+
};
|
|
1194
|
+
const receipt = { ...body, digest: (0, receipt_1.computeReceiptDigest)(body) };
|
|
1195
|
+
const dir = path_1.default.join(root, '.awm', 'sensors', 'runs');
|
|
1196
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
1197
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, `${head}.json`), JSON.stringify(receipt) + '\n');
|
|
1198
|
+
return (0, receipt_1.readReceipt)(root, head);
|
|
1199
|
+
};
|
|
1200
|
+
expect(mk(['src/**/*.ts'])).toMatchObject({ ok: true });
|
|
1201
|
+
expect(mk(['../secret/**'])).toEqual({ ok: false, code: 'receipt-invalid' });
|
|
1202
|
+
expect(mk(['!src/**'])).toEqual({ ok: false, code: 'receipt-invalid' });
|
|
1203
|
+
expect(mk(['src/{a,b}.ts'])).toEqual({ ok: false, code: 'receipt-invalid' });
|
|
1204
|
+
expect(mk(['src/@(a|b).ts'])).toEqual({ ok: false, code: 'receipt-invalid' });
|
|
1205
|
+
});
|
|
947
1206
|
});
|
|
@@ -0,0 +1,101 @@
|
|
|
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 child_process_1 = require("child_process");
|
|
10
|
+
const awm_gitignore_1 = require("../../../src/core/awm-gitignore");
|
|
11
|
+
const checks_1 = require("../../../src/core/diagnostics/checks");
|
|
12
|
+
function git(cwd, args) {
|
|
13
|
+
(0, child_process_1.execFileSync)('git', args, { cwd, stdio: 'pipe' });
|
|
14
|
+
}
|
|
15
|
+
const stubMachine = () => ({
|
|
16
|
+
registryCache: { present: true, gitState: 'clean' },
|
|
17
|
+
hook: { present: true, applicable: true },
|
|
18
|
+
devCore: { present: true, brokenLinks: [] },
|
|
19
|
+
ambient: { wanted: [], installed: [] },
|
|
20
|
+
contextInjection: [],
|
|
21
|
+
globalSkills: { valid: [], repairable: [], dead: [], usurped: [] },
|
|
22
|
+
});
|
|
23
|
+
const stubProject = (root) => ({
|
|
24
|
+
root,
|
|
25
|
+
profile: { present: false, extensions: [] },
|
|
26
|
+
activeBundles: { expected: [], linked: [], broken: [] },
|
|
27
|
+
orphanLinks: { repairable: [], dead: [], usurped: [] },
|
|
28
|
+
sensors: { present: true },
|
|
29
|
+
constitution: { present: true },
|
|
30
|
+
context: { present: true, file: 'AGENTS.md' },
|
|
31
|
+
});
|
|
32
|
+
describe('evaluateAwmGitignorePolicy + doctor project check', () => {
|
|
33
|
+
let repo;
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
repo = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-gi-doc-'));
|
|
36
|
+
git(repo, ['init']);
|
|
37
|
+
git(repo, ['config', 'user.email', 't@t']);
|
|
38
|
+
git(repo, ['config', 'user.name', 't']);
|
|
39
|
+
});
|
|
40
|
+
afterEach(() => {
|
|
41
|
+
fs_1.default.rmSync(repo, { recursive: true, force: true });
|
|
42
|
+
});
|
|
43
|
+
it('fails when local runtime is tracked and proposes git rm --cached', () => {
|
|
44
|
+
fs_1.default.mkdirSync(path_1.default.join(repo, '.awm', 'journal'), { recursive: true });
|
|
45
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.awm', 'journal', 'x.json'), '{}');
|
|
46
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.gitignore'), 'node_modules\n');
|
|
47
|
+
git(repo, ['add', '-A']);
|
|
48
|
+
git(repo, ['commit', '-m', 'track journal']);
|
|
49
|
+
const result = (0, awm_gitignore_1.evaluateAwmGitignorePolicy)(repo);
|
|
50
|
+
expect(result.ok).toBe(false);
|
|
51
|
+
if (result.ok)
|
|
52
|
+
return;
|
|
53
|
+
expect(result.detail).toMatch(/tracked local runtime/i);
|
|
54
|
+
expect(result.remedy).toMatch(/git rm --cached/);
|
|
55
|
+
expect(result.remedy).toMatch(/awm sensors init|awm watch init/);
|
|
56
|
+
});
|
|
57
|
+
it('fails when sensors.json exists but is ignored by bare .awm/', () => {
|
|
58
|
+
fs_1.default.mkdirSync(path_1.default.join(repo, '.awm'), { recursive: true });
|
|
59
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.awm', 'sensors.json'), '{}');
|
|
60
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.gitignore'), '.awm/\n');
|
|
61
|
+
const result = (0, awm_gitignore_1.evaluateAwmGitignorePolicy)(repo);
|
|
62
|
+
expect(result.ok).toBe(false);
|
|
63
|
+
if (result.ok)
|
|
64
|
+
return;
|
|
65
|
+
expect(result.detail).toMatch(/versioned config ignored/i);
|
|
66
|
+
expect(result.detail).toMatch(/managed.*block missing/i);
|
|
67
|
+
expect(result.remedy).toMatch(/awm sensors init|awm watch init/);
|
|
68
|
+
});
|
|
69
|
+
it('fails when markers exist but the block body is incomplete', () => {
|
|
70
|
+
fs_1.default.mkdirSync(path_1.default.join(repo, '.awm'), { recursive: true });
|
|
71
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.awm', 'sensors.json'), '{}');
|
|
72
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.gitignore'), '# AWM:GITIGNORE:BEGIN\n.awm/*\n# AWM:GITIGNORE:END\n');
|
|
73
|
+
const result = (0, awm_gitignore_1.evaluateAwmGitignorePolicy)(repo);
|
|
74
|
+
expect(result.ok).toBe(false);
|
|
75
|
+
if (result.ok)
|
|
76
|
+
return;
|
|
77
|
+
expect(result.detail).toMatch(/managed.*block missing|incomplete/i);
|
|
78
|
+
});
|
|
79
|
+
it('passes when the managed block is installed', () => {
|
|
80
|
+
fs_1.default.mkdirSync(path_1.default.join(repo, '.awm'), { recursive: true });
|
|
81
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.awm', 'sensors.json'), '{}');
|
|
82
|
+
(0, awm_gitignore_1.upsertAwmGitignore)(repo);
|
|
83
|
+
git(repo, ['add', '.gitignore', '.awm/sensors.json']);
|
|
84
|
+
git(repo, ['commit', '-m', 'ok']);
|
|
85
|
+
expect((0, awm_gitignore_1.evaluateAwmGitignorePolicy)(repo)).toMatchObject({ ok: true });
|
|
86
|
+
});
|
|
87
|
+
it('runChecks emits project.awm-gitignore missing when policy fails', () => {
|
|
88
|
+
fs_1.default.mkdirSync(path_1.default.join(repo, '.awm'), { recursive: true });
|
|
89
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.awm', 'sensors.json'), '{}');
|
|
90
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.gitignore'), '.awm/\n');
|
|
91
|
+
const ctx = {
|
|
92
|
+
machine: stubMachine(),
|
|
93
|
+
project: stubProject(repo),
|
|
94
|
+
providers: [],
|
|
95
|
+
};
|
|
96
|
+
const report = (0, checks_1.runChecks)(ctx);
|
|
97
|
+
const row = report.results.find((r) => r.id === 'project.awm-gitignore');
|
|
98
|
+
expect(row).toMatchObject({ status: 'missing' });
|
|
99
|
+
expect(report.overall).toBe('degraded');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -8,6 +8,11 @@ const path_1 = __importDefault(require("path"));
|
|
|
8
8
|
test('.awm (and therefore .awm/ledger) is gitignored — raw ledger never committed', () => {
|
|
9
9
|
const gitignore = fs_1.default.readFileSync(path_1.default.join(__dirname, '../../../../.gitignore'), 'utf-8');
|
|
10
10
|
const lines = gitignore.split('\n').map(l => l.trim());
|
|
11
|
-
|
|
11
|
+
// D-024: managed block uses `.awm/*` (with allowlist exceptions), not a bare `.awm` / `.awm/`.
|
|
12
|
+
const coversAwm = lines.includes('.awm/*') ||
|
|
13
|
+
lines.includes('.awm') ||
|
|
14
|
+
lines.includes('.awm/') ||
|
|
15
|
+
lines.includes('.awm/ledger') ||
|
|
16
|
+
lines.includes('.awm/ledger/');
|
|
12
17
|
expect(coversAwm).toBe(true);
|
|
13
18
|
});
|
|
@@ -0,0 +1,102 @@
|
|
|
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-CLI-DOCS — RF-3.3 sensor execution optimization CLI reference.
|
|
10
|
+
*
|
|
11
|
+
* Section-scoped asserts only: a whole-file keyword must not satisfy these.
|
|
12
|
+
* No registry file reads (CLI plan S5).
|
|
13
|
+
*/
|
|
14
|
+
const repoRoot = path_1.default.resolve(__dirname, '../../..');
|
|
15
|
+
const cliReferencePath = path_1.default.join(repoRoot, 'docs', 'cli-reference.md');
|
|
16
|
+
function readCliReference() {
|
|
17
|
+
return fs_1.default.readFileSync(cliReferencePath, 'utf8');
|
|
18
|
+
}
|
|
19
|
+
/** H2 Sensors block through the next H2. */
|
|
20
|
+
function sensorsSection(text) {
|
|
21
|
+
const start = text.indexOf('## Sensors (per-project computational checks)');
|
|
22
|
+
expect(start).toBeGreaterThanOrEqual(0);
|
|
23
|
+
const rest = text.slice(start);
|
|
24
|
+
const nextH2 = rest.search(/\n## (?!Sensors)/);
|
|
25
|
+
return nextH2 === -1 ? rest : rest.slice(0, nextH2);
|
|
26
|
+
}
|
|
27
|
+
/** Named H3 inside a parent section; fails if the heading is absent. */
|
|
28
|
+
function h3Section(parent, heading) {
|
|
29
|
+
const start = parent.indexOf(heading);
|
|
30
|
+
expect(start).toBeGreaterThanOrEqual(0);
|
|
31
|
+
const rest = parent.slice(start);
|
|
32
|
+
const next = rest.search(/\n### /);
|
|
33
|
+
return next === -1 ? rest : rest.slice(0, next);
|
|
34
|
+
}
|
|
35
|
+
/** Named H4 inside a parent section; fails if the heading is absent. */
|
|
36
|
+
function h4Section(parent, heading) {
|
|
37
|
+
const start = parent.indexOf(heading);
|
|
38
|
+
expect(start).toBeGreaterThanOrEqual(0);
|
|
39
|
+
const rest = parent.slice(start);
|
|
40
|
+
const next = rest.search(/\n#### /);
|
|
41
|
+
return next === -1 ? rest : rest.slice(0, next);
|
|
42
|
+
}
|
|
43
|
+
describe('sensor execution optimization docs contract (CMD-CLI-DOCS / RF-3.3)', () => {
|
|
44
|
+
it('documents authored inputs and sensor-level closureOnly under Sensors', () => {
|
|
45
|
+
const sensors = sensorsSection(readCliReference());
|
|
46
|
+
const section = h3Section(sensors, '### Inputs and closureOnly');
|
|
47
|
+
expect(section).toMatch(/`inputs`/);
|
|
48
|
+
expect(section).toMatch(/variant|project override|pack/i);
|
|
49
|
+
expect(section).toMatch(/no effective `inputs`[\s\S]{0,160}always execute|always execute[\s\S]{0,160}no effective `inputs`/i);
|
|
50
|
+
expect(section).toMatch(/`closureOnly`/);
|
|
51
|
+
expect(section).toMatch(/sensor-level|sensor object/i);
|
|
52
|
+
expect(section).toMatch(/mutation[\s\S]{0,120}closureOnly|closureOnly[\s\S]{0,120}mutation/i);
|
|
53
|
+
});
|
|
54
|
+
it('documents --slice and --no-cache on awm sensors run', () => {
|
|
55
|
+
const sensors = sensorsSection(readCliReference());
|
|
56
|
+
const run = h3Section(sensors, '### `awm sensors run`');
|
|
57
|
+
expect(run).toMatch(/--slice/);
|
|
58
|
+
expect(run).toMatch(/closureOnly/);
|
|
59
|
+
expect(run).toMatch(/--slice[\s\S]{0,200}(?:reject|refuse|not combine|cannot(?:\s+be)?\s+combin)[\s\S]{0,80}--(?:all|slow)|(?:--all|--slow)[\s\S]{0,120}(?:reject|refuse|not combine|cannot(?:\s+be)?\s+combin)[\s\S]{0,80}--slice/i);
|
|
60
|
+
expect(run).toMatch(/--no-cache/);
|
|
61
|
+
expect(run).toMatch(/isFullSelection|full[- ]gate|Change Record receipt/i);
|
|
62
|
+
expect(run).toMatch(/--slice[\s\S]{0,200}(?:not|never|no)[\s\S]{0,80}receipt|receipt[\s\S]{0,120}--slice/i);
|
|
63
|
+
});
|
|
64
|
+
it('documents cache hit semantics, provenance, fingerprint, and failureKind', () => {
|
|
65
|
+
const sensors = sensorsSection(readCliReference());
|
|
66
|
+
const cache = h4Section(h3Section(sensors, '### `awm sensors run`'), '#### Result cache');
|
|
67
|
+
expect(cache).toMatch(/cache hit|cached:\s*`?true`?/i);
|
|
68
|
+
expect(cache).toMatch(/originDirty/);
|
|
69
|
+
expect(cache).toMatch(/fingerprint|sensorFingerprint/i);
|
|
70
|
+
expect(cache).toMatch(/always-measured|alwaysMeasured/i);
|
|
71
|
+
expect(cache).toMatch(/live[\s\S]{0,80}version|liveState/i);
|
|
72
|
+
expect(cache).toMatch(/failureKind/);
|
|
73
|
+
expect(cache).toMatch(/findings/);
|
|
74
|
+
expect(cache).toMatch(/missing-binary|exit-code/);
|
|
75
|
+
expect(cache).toMatch(/\.awm\/sensors\/runs\//);
|
|
76
|
+
expect(cache).toMatch(/pre-baseline|before baseline|raw[\s\S]{0,40}baseline/i);
|
|
77
|
+
expect(cache).toMatch(/--changed[\s\S]{0,160}(?:not|never|must not)[\s\S]{0,80}(?:write|cache)|(?:write|cache)[\s\S]{0,120}(?:not|never)[\s\S]{0,80}--changed/i);
|
|
78
|
+
});
|
|
79
|
+
it('documents receipt schema 2 and RF-4.6 Change Record honesty', () => {
|
|
80
|
+
const sensors = sensorsSection(readCliReference());
|
|
81
|
+
const receipt = h4Section(h3Section(sensors, '### `awm sensors run`'), '#### Sensor receipt');
|
|
82
|
+
expect(receipt).toMatch(/"schema"\s*:\s*2|schema\s*`?2`?/);
|
|
83
|
+
expect(receipt).toMatch(/measurements/);
|
|
84
|
+
expect(receipt).toMatch(/alwaysMeasuredPaths|always-measured/i);
|
|
85
|
+
// RF-4.6 honesty only — "legacy late-path rules" elsewhere must not satisfy this.
|
|
86
|
+
expect(receipt).toMatch(/RF-4\.6[\s\S]{0,80}honesty[\s\S]{0,400}schema-1 late-path|schema-1 late-path[\s\S]{0,400}RF-4\.6[\s\S]{0,80}honesty/i);
|
|
87
|
+
expect(receipt).toMatch(/(?:any|every)[\s\S]{0,80}(?:without|no|lacks)[\s\S]{0,60}inputs[\s\S]{0,160}schema[\s\S]{0,40}1|schema[\s\S]{0,40}1[\s\S]{0,160}(?:without|no|lacks)[\s\S]{0,60}inputs/i);
|
|
88
|
+
expect(receipt).toMatch(/docs-only|documentation-only|docs only/i);
|
|
89
|
+
expect(receipt).toMatch(/project override/i);
|
|
90
|
+
});
|
|
91
|
+
it('documents RF-4.7 change new remedy honesty (full-gate + cache reuse)', () => {
|
|
92
|
+
const text = readCliReference();
|
|
93
|
+
const start = text.indexOf('### `awm change new <slug>`');
|
|
94
|
+
expect(start).toBeGreaterThanOrEqual(0);
|
|
95
|
+
const rest = text.slice(start);
|
|
96
|
+
const nextH3 = rest.search(/\n### /);
|
|
97
|
+
const section = nextH3 === -1 ? rest : rest.slice(0, nextH3);
|
|
98
|
+
// Must match REMEDY_RECEIPT honesty — not a bare `awm sensors run` that implies every binary re-execs.
|
|
99
|
+
expect(section).toMatch(/receipt-missing[\s\S]{0,200}full-gate[\s\S]{0,80}awm sensors run[\s\S]{0,120}cache may reuse/i);
|
|
100
|
+
expect(section.toLowerCase()).not.toMatch(/every tool|must re-exec|reexecute every/);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
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-DECISION — RF-D.1 `--slice` / `closureOnly` cycle policy in docs/decisions.md.
|
|
10
|
+
*
|
|
11
|
+
* Section-scoped: index-table mentions alone must not satisfy the body asserts.
|
|
12
|
+
*/
|
|
13
|
+
const repoRoot = path_1.default.resolve(__dirname, '../../..');
|
|
14
|
+
const decisionsPath = path_1.default.join(repoRoot, 'docs', 'decisions.md');
|
|
15
|
+
const DECISION_ID = 'D-025';
|
|
16
|
+
const DECISION_HEADING = `## ${DECISION_ID}`;
|
|
17
|
+
const COMPANION_PLAN = 'docs/plans/2026-10-27-sensor-execution-optimization-registry-plan.md';
|
|
18
|
+
function readDecisions() {
|
|
19
|
+
return fs_1.default.readFileSync(decisionsPath, 'utf8');
|
|
20
|
+
}
|
|
21
|
+
/** Body of ## D-025 through the next ## D- or EOF. */
|
|
22
|
+
function decisionBody(text) {
|
|
23
|
+
const start = text.indexOf(DECISION_HEADING);
|
|
24
|
+
expect(start).toBeGreaterThanOrEqual(0);
|
|
25
|
+
const rest = text.slice(start);
|
|
26
|
+
const next = rest.search(/\n## D-\d+/);
|
|
27
|
+
return next === -1 ? rest : rest.slice(0, next);
|
|
28
|
+
}
|
|
29
|
+
describe('sensor slice decision contract (CMD-DECISION / RF-D.1)', () => {
|
|
30
|
+
it('indexes D-025 in the decisions table', () => {
|
|
31
|
+
const text = readDecisions();
|
|
32
|
+
const tableEnd = text.indexOf('\n---\n');
|
|
33
|
+
expect(tableEnd).toBeGreaterThan(0);
|
|
34
|
+
const table = text.slice(0, tableEnd);
|
|
35
|
+
expect(table).toMatch(new RegExp(`\\[D-025\\]\\(#d-025\\)[^\n]*--slice[^\n]*closureOnly|\\[D-025\\]\\(#d-025\\)[^\n]*closureOnly[^\n]*--slice`, 'i'));
|
|
36
|
+
expect(table).toMatch(/\| \[D-025\]\(#d-025\) \| 2026-10-27 \|/);
|
|
37
|
+
});
|
|
38
|
+
it('records --slice + closureOnly cycle policy and forbids partial selection as closure evidence', () => {
|
|
39
|
+
const body = decisionBody(readDecisions());
|
|
40
|
+
expect(body).toMatch(/--slice/);
|
|
41
|
+
expect(body).toMatch(/closureOnly/);
|
|
42
|
+
// DONE of a slice requires `awm sensors run --slice` + overall pass — not a loose
|
|
43
|
+
// "slice…DONE" / "DONE…--slice" alternation that surviving prose elsewhere could satisfy.
|
|
44
|
+
expect(body).toMatch(/(?:Task\s*\/\s*slice|slice)\s+DONE:\s*`awm sensors run --slice`[\s\S]{0,80}overall:\s*`?pass`?/i);
|
|
45
|
+
expect(body).toMatch(/(?:full[- ]gate|full run|no --slice)[\s\S]{0,200}(?:QA|docs|finish|Change Record)|(?:QA|docs|finish|Change Record)[\s\S]{0,200}(?:full[- ]gate|full run)/i);
|
|
46
|
+
// Partial flags are never closure evidence — all four must sit in that rule
|
|
47
|
+
// (section-scoped to D-025 body; deleting the bullet fails).
|
|
48
|
+
expect(body).toMatch(/`--slice`[\s\S]{0,100}`--fast`[\s\S]{0,100}`--slow`[\s\S]{0,100}`--changed`[\s\S]{0,160}(?:nunca|never)[\s\S]{0,100}closure/i);
|
|
49
|
+
});
|
|
50
|
+
it('points to the registry companion plan for skill wording', () => {
|
|
51
|
+
const body = decisionBody(readDecisions());
|
|
52
|
+
expect(body).toContain(COMPANION_PLAN);
|
|
53
|
+
expect(body).toMatch(/awm-baseline-registry/);
|
|
54
|
+
expect(body).toMatch(/skill/i);
|
|
55
|
+
});
|
|
56
|
+
it('avoids incomplete-prose sentinels in the decision body', () => {
|
|
57
|
+
const body = decisionBody(readDecisions());
|
|
58
|
+
expect(body).not.toMatch(/\bTODO\b/);
|
|
59
|
+
expect(body).not.toMatch(/\bTBD\b/);
|
|
60
|
+
expect(body).not.toMatch(/\bplaceholder\b/i);
|
|
61
|
+
expect(body).not.toMatch(/\bdraft\b/i);
|
|
62
|
+
});
|
|
63
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-workflow-manager",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.22.0",
|
|
4
4
|
"main": "dist/src/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"awm": "./dist/src/index.js"
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"commander": "^14.0.3",
|
|
54
54
|
"js-yaml": "4.3.2",
|
|
55
55
|
"picocolors": "^1.1.1",
|
|
56
|
+
"picomatch": "4.0.7",
|
|
56
57
|
"semver": "^7.7.4",
|
|
57
58
|
"simple-git": "3.36.0"
|
|
58
59
|
},
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|