agentic-workflow-manager 9.2.1 → 9.4.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,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.exitCodeFor = exitCodeFor;
4
+ exports.formatReport = formatReport;
5
+ exports.registerPlanCommand = registerPlanCommand;
6
+ const validate_1 = require("../../core/plan/validate");
7
+ const SUPPORTED_SCHEMA = 'compact-slices/v1';
8
+ const MAX_PATH_LENGTH = 4096;
9
+ const MAX_DIAGNOSTICS = 20;
10
+ const MAX_DIAGNOSTIC_LENGTH = 4096;
11
+ function assertText(value, name) {
12
+ if (typeof value !== 'string' || value.length === 0 || value.length > MAX_PATH_LENGTH || value.startsWith('--') || /[\u0000-\u001F\u007F-\u009F]/.test(value)) {
13
+ throw new Error(`${name} must be a non-empty path without control characters`);
14
+ }
15
+ }
16
+ function terminalSafe(value) {
17
+ return value.replace(/[\u0000-\u001F\u007F-\u009F]/g, character => `\\u${character.codePointAt(0).toString(16).padStart(4, '0')}`);
18
+ }
19
+ function assertDependencies(deps) {
20
+ if (!deps || typeof deps.validatePlanFile !== 'function')
21
+ throw new Error('validatePlanFile must be a function');
22
+ }
23
+ function boundedDiagnostics(diagnostics) {
24
+ if (!Array.isArray(diagnostics))
25
+ throw new Error('plan validator returned invalid diagnostics');
26
+ return diagnostics.slice(0, MAX_DIAGNOSTICS).map((diagnostic) => {
27
+ if (!diagnostic || typeof diagnostic.code !== 'string' || typeof diagnostic.message !== 'string') {
28
+ throw new Error('plan validator returned an invalid diagnostic');
29
+ }
30
+ return {
31
+ code: diagnostic.code.slice(0, MAX_DIAGNOSTIC_LENGTH),
32
+ message: diagnostic.message.slice(0, MAX_DIAGNOSTIC_LENGTH),
33
+ ...(typeof diagnostic.field === 'string' ? { field: diagnostic.field.slice(0, MAX_DIAGNOSTIC_LENGTH) } : {}),
34
+ };
35
+ });
36
+ }
37
+ function reportPayload(report, planPath) {
38
+ switch (report.state) {
39
+ case 'valid':
40
+ return {
41
+ state: report.state, path: planPath, schema: report.schema, planId: report.manifest.planId,
42
+ requirements: report.manifest.requirements.length, sources: report.manifest.sources.length,
43
+ commands: report.manifest.commands.length, slices: report.manifest.slices.length, completeOwnership: true,
44
+ };
45
+ case 'legacy':
46
+ return { state: report.state, path: planPath, message: 'existing full-quality path applies; compact optimization was not requested.' };
47
+ case 'invalid':
48
+ return { state: report.state, path: planPath, diagnostics: boundedDiagnostics(report.diagnostics) };
49
+ case 'unsupported':
50
+ return {
51
+ state: report.state, path: planPath, schema: report.schema, supportedSchema: SUPPORTED_SCHEMA,
52
+ diagnostics: boundedDiagnostics(report.diagnostics), remedy: 'update AWM to support this compact plan schema.',
53
+ };
54
+ default:
55
+ throw new Error('plan validator returned an unknown report state');
56
+ }
57
+ }
58
+ function exitCodeFor(report) {
59
+ return report.state === 'valid' || report.state === 'legacy' ? 0 : 2;
60
+ }
61
+ function formatReport(report, planPath) {
62
+ const payload = reportPayload(report, planPath);
63
+ switch (report.state) {
64
+ case 'valid':
65
+ return `Plan validation: valid "${terminalSafe(planPath)}" (${terminalSafe(report.schema)}; ${payload.slices} slices; ${payload.requirements} requirements; complete ownership)\n`;
66
+ case 'legacy':
67
+ return `Plan validation: legacy "${terminalSafe(planPath)}" — existing full-quality path applies; compact optimization was not requested.\n`;
68
+ case 'invalid':
69
+ return `Plan validation: invalid "${terminalSafe(planPath)}"\n${payload.diagnostics.map(diagnostic => `- ${terminalSafe(diagnostic.code)}: ${terminalSafe(diagnostic.message)}${diagnostic.field ? ` (${terminalSafe(diagnostic.field)})` : ''}`).join('\n')}\n`;
70
+ case 'unsupported':
71
+ return `Plan validation: unsupported "${terminalSafe(planPath)}" (${terminalSafe(report.schema)}; supported: ${SUPPORTED_SCHEMA})\nUpdate AWM to support this compact plan schema.\n`;
72
+ default:
73
+ throw new Error('plan validator returned an unknown report state');
74
+ }
75
+ }
76
+ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.validatePlanFile }) {
77
+ if (!program || typeof program.command !== 'function')
78
+ throw new Error('program must be a Commander command');
79
+ assertDependencies(deps);
80
+ const plan = program.command('plan').description('inspect plan contracts');
81
+ plan
82
+ .command('validate <plan-path>')
83
+ .description('validate a compact plan without modifying it')
84
+ .option('--json', 'emit one stable JSON report')
85
+ .option('--cwd <path>', 'repository root for plan containment and source resolution')
86
+ .action((planPath, options) => {
87
+ assertText(planPath, 'plan path');
88
+ const cwd = options.cwd ?? process.cwd();
89
+ assertText(cwd, '--cwd');
90
+ const report = deps.validatePlanFile(planPath, cwd);
91
+ const output = options.json === true
92
+ ? `${JSON.stringify(reportPayload(report, planPath))}\n`
93
+ : formatReport(report, planPath);
94
+ process.stdout.write(output);
95
+ const code = exitCodeFor(report);
96
+ process.exitCode = code;
97
+ });
98
+ }
@@ -13,6 +13,10 @@ const init_1 = require("../sensors/init");
13
13
  const baseline_1 = require("../sensors/baseline");
14
14
  const run_1 = require("../sensors/run");
15
15
  const paths_1 = require("../../core/paths");
16
+ const registries_1 = require("../../core/registries");
17
+ const inspect_1 = require("../../core/context-kernel/inspect");
18
+ const registries_2 = require("../../core/registries");
19
+ const check_1 = require("../../core/currentness/check");
16
20
  const MANIFEST = path_1.default.join('.awm', 'sensors.json');
17
21
  /**
18
22
  * The agent needs project context delivered every session. A repo with neither file
@@ -31,6 +35,54 @@ function checkContext(cwd) {
31
35
  }
32
36
  return { id: 'context', ok: true, detail: present.join(', ') };
33
37
  }
38
+ function checkContextKernel(cwd) {
39
+ let resolution;
40
+ try {
41
+ resolution = (0, registries_1.resolveProjectContextSchema)();
42
+ }
43
+ catch {
44
+ return {
45
+ id: 'context-kernel', ok: false, advisory: false,
46
+ detail: 'configured registry inventory could not be read',
47
+ remedy: 'Repair the local registry inventory and rerun preflight.',
48
+ };
49
+ }
50
+ if (resolution.diagnostics.length > 0) {
51
+ return {
52
+ id: 'context-kernel',
53
+ ok: false,
54
+ advisory: false,
55
+ detail: `Context Kernel registry declaration is invalid: ${resolution.diagnostics.join('; ')}`,
56
+ remedy: 'repair the registry manifest or use the safe full-context project files until the registry is valid',
57
+ };
58
+ }
59
+ if (!resolution.declaration)
60
+ return null;
61
+ const inspection = (0, inspect_1.inspectContextKernel)(cwd);
62
+ if (inspection.state === 'legacy') {
63
+ return {
64
+ id: 'context-kernel',
65
+ ok: false,
66
+ advisory: true,
67
+ detail: 'legacy full context — Context Kernel v1 migration available',
68
+ remedy: 'run project-context-init and review the generated rule trace; awm update never rewrites project files',
69
+ };
70
+ }
71
+ if (inspection.state === 'valid') {
72
+ return {
73
+ id: 'context-kernel',
74
+ ok: true,
75
+ detail: `Context Kernel v${inspection.schema} valid (${inspection.fixedBytes} fixed bytes)`,
76
+ };
77
+ }
78
+ return {
79
+ id: 'context-kernel',
80
+ ok: false,
81
+ advisory: false,
82
+ detail: inspection.detail,
83
+ remedy: inspection.remedy,
84
+ };
85
+ }
34
86
  function readManifest(cwd) {
35
87
  try {
36
88
  return JSON.parse(fs_1.default.readFileSync(path_1.default.join(cwd, MANIFEST), 'utf-8'));
@@ -339,14 +391,53 @@ function checkHost(cwd) {
339
391
  // Bitbucket, Azure DevOps, an internal git server, etc. — don't overclaim support.
340
392
  return { id: 'host', ok: true, detail: 'git host not recognized (github/gitlab) — PR/MR automation not applicable' };
341
393
  }
394
+ function bounded(value, limit = 512) {
395
+ return value.length <= limit ? value : `${value.slice(0, limit - 1)}…`;
396
+ }
397
+ function checkCompatibility() {
398
+ let failures;
399
+ try {
400
+ failures = (0, registries_2.verifyMinCliVersions)();
401
+ }
402
+ catch {
403
+ return {
404
+ id: 'compatibility', ok: false,
405
+ detail: 'configured registry inventory could not be read',
406
+ remedy: 'Repair the local registry inventory and rerun strict preflight.',
407
+ };
408
+ }
409
+ if (failures.length === 0)
410
+ return { id: 'compatibility', ok: true, detail: 'all configured registry minCliVersion requirements are satisfied' };
411
+ return {
412
+ id: 'compatibility', ok: false,
413
+ detail: bounded(failures.map(failure => `${failure.name} requires CLI >= ${failure.min}`).join('; ')),
414
+ remedy: 'npm i -g agentic-workflow-manager@latest && rerun in a fresh process',
415
+ };
416
+ }
417
+ function checkAuthoritativeCurrentness(report) {
418
+ const failing = report.components.filter(component => component.status !== 'current');
419
+ const components = report.components.map(component => bounded(`${component.component}: installed=${component.installed ?? 'unknown'} latest=${component.latest ?? 'unknown'} `
420
+ + `channel=${component.channel} source=${component.source}${component.pin ? ` pin=${component.pin}` : ''} `
421
+ + `checkedAt=${component.checkedAt} status=${component.status}`));
422
+ return failing.length === 0
423
+ ? { id: 'currentness', ok: true, detail: `Currentness: current. ${components.join('; ')}` }
424
+ : {
425
+ id: 'currentness', ok: false, detail: `Currentness: ${failing.map(component => component.status).join(', ')}. ${components.join('; ')}`,
426
+ remedy: bounded(failing.map(component => component.remedy).join('; ')),
427
+ };
428
+ }
342
429
  async function preflight(cwd = process.cwd(), opts = {}) {
343
- if (!opts || typeof opts !== 'object' || Array.isArray(opts) || (opts.verifySensors !== undefined && typeof opts.verifySensors !== 'boolean')) {
344
- throw new Error('preflight options must contain an optional boolean verifySensors');
430
+ if (!opts || typeof opts !== 'object' || Array.isArray(opts)
431
+ || (opts.verifySensors !== undefined && typeof opts.verifySensors !== 'boolean')
432
+ || (opts.requireCurrent !== undefined && typeof opts.requireCurrent !== 'boolean')) {
433
+ throw new Error('preflight options must contain optional boolean verifySensors and requireCurrent');
345
434
  }
346
435
  const manifest = readManifest(cwd);
347
436
  const manifestExists = fs_1.default.existsSync(path_1.default.join(cwd, MANIFEST));
437
+ const contextKernel = checkContextKernel(cwd);
348
438
  const checks = [
349
439
  checkContext(cwd),
440
+ ...(contextKernel ? [contextKernel] : []),
350
441
  checkManifest(cwd, manifest),
351
442
  // Skipped when there is no manifest: reporting "tools broken" (or nudging toward
352
443
  // a baseline that has nothing to snapshot) on a repo that was never set up
@@ -355,12 +446,26 @@ async function preflight(cwd = process.cwd(), opts = {}) {
355
446
  // The empirical mode is intentionally opt-in: ordinary preflight remains a
356
447
  // quick static inspection and must not dispatch project software.
357
448
  ...(opts.verifySensors === true ? [await checkSensorExecution(cwd)] : []),
449
+ // Strict mode is the only path that contacts authoritative npm/Git sources.
450
+ ...(opts.requireCurrent === true ? [checkCompatibility()] : []),
358
451
  // Runs unconditionally — orthogonal to sensor configuration entirely, this is
359
452
  // about PR/MR tooling, not sensors.
360
453
  checkHost(cwd),
361
454
  ];
362
- const status = !manifestExists ? 'not_configured'
363
- : checks.every(c => c.ok) ? 'ready'
364
- : 'degraded';
365
- return { status, checks };
455
+ const blockingFailure = (check) => !check.ok && check.advisory !== true;
456
+ const invalidContextKernel = contextKernel !== null && blockingFailure(contextKernel);
457
+ const status = invalidContextKernel ? 'degraded'
458
+ : !manifestExists ? 'not_configured'
459
+ : checks.some(blockingFailure) ? 'degraded'
460
+ : 'ready';
461
+ const currentness = opts.requireCurrent === true ? await (0, check_1.checkCurrentness)(cwd) : undefined;
462
+ if (currentness) {
463
+ checks.splice(checks.length - 1, 0, checkAuthoritativeCurrentness(currentness));
464
+ }
465
+ const strictFailure = checks.some(blockingFailure);
466
+ const strictStatus = invalidContextKernel ? 'degraded'
467
+ : !manifestExists ? 'not_configured'
468
+ : strictFailure ? 'degraded'
469
+ : 'ready';
470
+ return currentness ? { status: strictStatus, checks, currentness } : { status, checks };
366
471
  }
@@ -25,19 +25,18 @@ function formatReport(report) {
25
25
  // longer `PreflightCheck['id']` is added (confirmed: 'sensors-baseline', at 16
26
26
  // chars, broke a hardcoded 9-char pad).
27
27
  const idWidth = Math.max(0, ...report.checks.map(c => c.id.length));
28
- const lines = report.checks.map(c => ` ${c.ok ? picocolors_1.default.green('✔') : picocolors_1.default.red('✘')} ${c.id.padEnd(idWidth)} ${c.detail}`
28
+ const lines = report.checks.map(c => ` ${c.advisory === true ? picocolors_1.default.yellow('⚠') : c.ok ? picocolors_1.default.green('✔') : picocolors_1.default.red('✘')} ${c.id.padEnd(idWidth)} ${c.detail}`
29
29
  + (c.remedy ? `\n ${picocolors_1.default.dim('→ ' + c.remedy)}` : ''));
30
30
  if (report.status === 'ready') {
31
31
  return `${picocolors_1.default.green('✔')} Harness ready — this project can be gated.\n${lines.join('\n')}\n`;
32
32
  }
33
33
  const headline = report.status === 'not_configured'
34
34
  ? `${picocolors_1.default.red('✘')} AWM is not configured in this project.`
35
- : `${picocolors_1.default.red('✘')} Harness degraded — it declares sensors it cannot run.`;
35
+ : `${picocolors_1.default.red('✘')} Harness degraded — it has blocking preflight failures.`;
36
36
  return `${headline}\n${lines.join('\n')}\n\n`
37
37
  + ` ${picocolors_1.default.bold('Do not hand this off to an unattended run.')} Every quality phase downstream\n`
38
- + ` (implementer, reviewers, post-qa) consumes \`awm sensors run\`. With the harness in\n`
39
- + ` this state the gate reports on checks that never ran, and nobody finds out until a\n`
40
- + ` bad change is already merged.\n`;
38
+ + ` (implementer, reviewers, post-qa) consumes these gates. Resolve every blocking\n`
39
+ + ` check before handoff, so a bad change is not merged.\n`;
41
40
  }
42
41
  function registerPreflightCommand(program) {
43
42
  program
@@ -45,9 +44,13 @@ function registerPreflightCommand(program) {
45
44
  .description('verify the project harness can actually gate before development starts')
46
45
  .option('--json', 'emit the report as JSON')
47
46
  .option('--verify-sensors', 'run the complete sensor gate before unattended execution')
47
+ .option('--require-current', 'require authoritative CLI and registry currentness before unattended execution')
48
48
  .option('--cwd <path>', 'project directory to check (default: current)')
49
49
  .action(async (opts) => {
50
- const report = await (0, checks_1.preflight)(opts.cwd ?? process.cwd(), { verifySensors: opts.verifySensors === true });
50
+ const report = await (0, checks_1.preflight)(opts.cwd ?? process.cwd(), {
51
+ verifySensors: opts.verifySensors === true,
52
+ requireCurrent: opts.requireCurrent === true,
53
+ });
51
54
  process.stdout.write(opts.json ? JSON.stringify(report, null, 2) + '\n' : formatReport(report));
52
55
  const code = exitCodeFor(report);
53
56
  // `process.exit()` may truncate the JSON written immediately above when
@@ -0,0 +1,221 @@
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.inspectContextKernel = inspectContextKernel;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const INDEX_PATH = ['.awm', 'context', 'index.json'];
10
+ const ROOT_CONTEXT_FILES = ['AGENTS.md', 'CONSTITUTION.md', 'CLAUDE.md'];
11
+ const START_MARKER = '<!-- AWM:CONTEXT-KERNEL:START v1 -->';
12
+ const END_MARKER = '<!-- AWM:CONTEXT-KERNEL:END v1 -->';
13
+ const ANCHOR_MARKER_PREFIX = '<!-- awm-context:';
14
+ const TOP_LEVEL_FIELDS = ['schema', 'kernelFiles', 'maxFixedBytes', 'entries'];
15
+ const ENTRY_FIELDS = ['id', 'tier', 'path', 'anchor', 'when'];
16
+ const REMEDY = 'run project-context-init and review the Context Kernel v1 artifacts';
17
+ function invalid(detail) {
18
+ return { state: 'invalid', detail, remedy: REMEDY };
19
+ }
20
+ function within(root, candidate) {
21
+ const relative = path_1.default.relative(root, candidate);
22
+ return relative === '' || (!relative.startsWith(`..${path_1.default.sep}`) && relative !== '..' && !path_1.default.isAbsolute(relative));
23
+ }
24
+ function count(text, needle) {
25
+ return text.split(needle).length - 1;
26
+ }
27
+ function exactFields(raw, fields) {
28
+ const keys = Object.keys(raw);
29
+ return keys.length === fields.length && fields.every((field) => Object.prototype.hasOwnProperty.call(raw, field));
30
+ }
31
+ function isNormalizedRelative(value) {
32
+ if (typeof value !== 'string' || value.length === 0 || path_1.default.isAbsolute(value) || path_1.default.win32.isAbsolute(value))
33
+ return false;
34
+ if (value.includes('\\') || value.split('/').some((part) => part === '.' || part === '..' || part === ''))
35
+ return false;
36
+ return path_1.default.posix.normalize(value) === value;
37
+ }
38
+ function resolveRegularInside(root, relative) {
39
+ if (!isNormalizedRelative(relative))
40
+ return { detail: `invalid repository-relative path ${JSON.stringify(relative)}` };
41
+ const candidate = path_1.default.join(root, relative);
42
+ try {
43
+ const stat = fs_1.default.statSync(candidate);
44
+ if (!stat.isFile())
45
+ return { detail: `${relative} must resolve to a regular file` };
46
+ const real = fs_1.default.realpathSync(candidate);
47
+ if (!within(root, real))
48
+ return { detail: `${relative} resolves outside the project root` };
49
+ return { file: real };
50
+ }
51
+ catch (error) {
52
+ return { detail: `${relative} cannot be read: ${error instanceof Error ? error.message : String(error)}` };
53
+ }
54
+ }
55
+ function parseEntry(raw) {
56
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw) || !exactFields(raw, ENTRY_FIELDS)) {
57
+ return { detail: 'each index entry must contain exactly id, tier, path, anchor, when' };
58
+ }
59
+ const value = raw;
60
+ if (typeof value.id !== 'string' || !/^CTX-[A-Z0-9]+(?:-[A-Z0-9]+)*$/.test(value.id) || Buffer.byteLength(value.id, 'utf8') > 80) {
61
+ return { detail: 'entry id must match CTX-[A-Z0-9-]+ and be at most 80 bytes' };
62
+ }
63
+ if (value.tier !== 'kernel' && value.tier !== 'selective')
64
+ return { detail: 'entry tier must be kernel or selective' };
65
+ if (!isNormalizedRelative(value.path))
66
+ return { detail: `invalid repository-relative path ${JSON.stringify(value.path)}` };
67
+ for (const field of ['anchor', 'when']) {
68
+ if (typeof value[field] !== 'string' || value[field].length === 0 || Buffer.byteLength(value[field], 'utf8') > 500) {
69
+ return { detail: `entry ${field} must be a non-empty string of at most 500 bytes` };
70
+ }
71
+ }
72
+ return { entry: { id: value.id, tier: value.tier, path: value.path, anchor: value.anchor, when: value.when } };
73
+ }
74
+ function parseIndex(contents) {
75
+ let raw;
76
+ try {
77
+ raw = JSON.parse(contents);
78
+ }
79
+ catch (error) {
80
+ return { detail: `index is not valid JSON: ${error instanceof Error ? error.message : String(error)}` };
81
+ }
82
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw) || !exactFields(raw, TOP_LEVEL_FIELDS)) {
83
+ return { detail: 'index must contain exactly schema, kernelFiles, maxFixedBytes, entries' };
84
+ }
85
+ const value = raw;
86
+ if (value.schema !== 1 || !Number.isSafeInteger(value.schema))
87
+ return { detail: 'index schema must be exactly 1' };
88
+ if (!Array.isArray(value.kernelFiles) || value.kernelFiles.length === 0 || value.kernelFiles.some((file) => !isNormalizedRelative(file)) || new Set(value.kernelFiles).size !== value.kernelFiles.length) {
89
+ return { detail: 'kernelFiles must be a non-empty unique normalized path array' };
90
+ }
91
+ if (value.kernelFiles.some((file) => file.includes('/')))
92
+ return { detail: 'kernelFiles must name root context files' };
93
+ if (!Number.isSafeInteger(value.maxFixedBytes) || value.maxFixedBytes < 1)
94
+ return { detail: 'maxFixedBytes must be a positive integer' };
95
+ if (!Array.isArray(value.entries) || value.entries.length === 0)
96
+ return { detail: 'entries must be a non-empty array' };
97
+ const entries = [];
98
+ for (const rawEntry of value.entries) {
99
+ const parsed = parseEntry(rawEntry);
100
+ if (!parsed.entry)
101
+ return { detail: parsed.detail };
102
+ entries.push(parsed.entry);
103
+ }
104
+ if (new Set(entries.map((entry) => entry.id)).size !== entries.length)
105
+ return { detail: 'entry ids must be unique' };
106
+ if (new Set(entries.map((entry) => entry.anchor)).size !== entries.length)
107
+ return { detail: 'entry anchors must be unique' };
108
+ return { index: { schema: 1, kernelFiles: value.kernelFiles, maxFixedBytes: value.maxFixedBytes, entries } };
109
+ }
110
+ function markerBounds(contents) {
111
+ if (count(contents, START_MARKER) !== 1 || count(contents, END_MARKER) !== 1)
112
+ return null;
113
+ const start = contents.indexOf(START_MARKER) + START_MARKER.length;
114
+ const end = contents.indexOf(END_MARKER);
115
+ return start < end ? { start, end } : null;
116
+ }
117
+ /** Inspects project-owned Context Kernel v1 artifacts. Invalid project artifacts are
118
+ * classified explicitly; only invalid public input throws. */
119
+ function inspectContextKernel(cwd) {
120
+ if (typeof cwd !== 'string' || cwd.length === 0)
121
+ throw new Error('cwd must be a non-empty directory path');
122
+ let root;
123
+ try {
124
+ root = fs_1.default.realpathSync(cwd);
125
+ if (!fs_1.default.statSync(root).isDirectory())
126
+ throw new Error('not a directory');
127
+ }
128
+ catch (error) {
129
+ throw new Error(`cwd must resolve to an existing directory: ${error instanceof Error ? error.message : String(error)}`);
130
+ }
131
+ const indexFile = path_1.default.join(root, ...INDEX_PATH);
132
+ let indexExists;
133
+ try {
134
+ fs_1.default.lstatSync(indexFile);
135
+ indexExists = true;
136
+ }
137
+ catch (error) {
138
+ if (error.code !== 'ENOENT')
139
+ return invalid(`${indexFile} cannot be inspected`);
140
+ indexExists = false;
141
+ }
142
+ if (!indexExists) {
143
+ for (const name of ROOT_CONTEXT_FILES) {
144
+ const candidate = path_1.default.join(root, name);
145
+ try {
146
+ if (fs_1.default.existsSync(candidate) && fs_1.default.statSync(candidate).isFile()) {
147
+ const contents = fs_1.default.readFileSync(candidate, 'utf8');
148
+ if (!contents.includes('AWM:CONTEXT-KERNEL:') && !contents.includes(ANCHOR_MARKER_PREFIX))
149
+ continue;
150
+ return invalid(`${name} contains Context Kernel markers but ${INDEX_PATH.join('/')} is missing`);
151
+ }
152
+ }
153
+ catch (error) {
154
+ return invalid(`${name} cannot be inspected: ${error instanceof Error ? error.message : String(error)}`);
155
+ }
156
+ }
157
+ return { state: 'legacy' };
158
+ }
159
+ const indexTarget = resolveRegularInside(root, INDEX_PATH.join('/'));
160
+ if (!indexTarget.file)
161
+ return invalid(`${INDEX_PATH.join('/')}: ${indexTarget.detail}`);
162
+ let parsed;
163
+ try {
164
+ parsed = parseIndex(fs_1.default.readFileSync(indexTarget.file, 'utf8'));
165
+ }
166
+ catch (error) {
167
+ return invalid(`${INDEX_PATH.join('/')}: cannot be read: ${error instanceof Error ? error.message : String(error)}`);
168
+ }
169
+ if (!parsed.index)
170
+ return invalid(`${INDEX_PATH.join('/')}: ${parsed.detail}`);
171
+ const index = parsed.index;
172
+ const files = new Map();
173
+ for (const kernelFile of index.kernelFiles) {
174
+ const target = resolveRegularInside(root, kernelFile);
175
+ if (!target.file)
176
+ return invalid(`${kernelFile}: ${target.detail}`);
177
+ const contents = fs_1.default.readFileSync(target.file, 'utf8');
178
+ if (!markerBounds(contents))
179
+ return invalid(`${kernelFile}: requires exactly one ordered Context Kernel v1 marker pair`);
180
+ files.set(kernelFile, contents);
181
+ }
182
+ for (const entry of index.entries) {
183
+ const target = resolveRegularInside(root, entry.path);
184
+ if (!target.file)
185
+ return invalid(`${entry.path}: ${target.detail}`);
186
+ let contents;
187
+ try {
188
+ contents = files.get(entry.path) ?? fs_1.default.readFileSync(target.file, 'utf8');
189
+ }
190
+ catch (error) {
191
+ return invalid(`${entry.path}: cannot be read: ${error instanceof Error ? error.message : String(error)}`);
192
+ }
193
+ const marker = `<!-- ${entry.anchor} -->`;
194
+ if (count(contents, marker) !== 1)
195
+ return invalid(`${entry.path}: anchor ${entry.anchor} must appear exactly once`);
196
+ const anchor = contents.indexOf(marker);
197
+ const bounds = files.has(entry.path) ? markerBounds(contents) : null;
198
+ if (entry.tier === 'kernel') {
199
+ if (!files.has(entry.path))
200
+ return invalid(`${entry.path}: kernel entry must point to a kernel file`);
201
+ if (!bounds || anchor < bounds.start || anchor >= bounds.end)
202
+ return invalid(`${entry.path}: kernel anchor ${entry.anchor} must be inside the protected region`);
203
+ }
204
+ else if (bounds && anchor >= bounds.start && anchor < bounds.end) {
205
+ return invalid(`${entry.path}: selective anchor ${entry.anchor} must be outside the protected region`);
206
+ }
207
+ }
208
+ let fixedBytes = 0;
209
+ for (const name of ROOT_CONTEXT_FILES) {
210
+ const candidate = path_1.default.join(root, name);
211
+ if (!fs_1.default.existsSync(candidate))
212
+ continue;
213
+ const target = resolveRegularInside(root, name);
214
+ if (!target.file)
215
+ return invalid(`${name}: ${target.detail}`);
216
+ fixedBytes += fs_1.default.statSync(target.file).size;
217
+ }
218
+ if (fixedBytes > index.maxFixedBytes)
219
+ return invalid(`fixed context bytes ${fixedBytes} exceed maxFixedBytes ${index.maxFixedBytes}`);
220
+ return { state: 'valid', schema: 1, index, fixedBytes };
221
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });