@atolis-hq/wake 0.3.17 → 0.3.19
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.
|
@@ -2,9 +2,10 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
4
|
import { parseRootConfig } from './root-schema.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
5
|
+
const nodeFileReader = { readFile: (path) => readFile(path, 'utf8') };
|
|
6
|
+
export async function loadConfig(wakeRoot, fileSystem = nodeFileReader) {
|
|
7
|
+
const main = await readYaml(join(wakeRoot, 'config.yaml'), fileSystem);
|
|
8
|
+
const workflows = await readYamlIfPresent(join(wakeRoot, 'config.workflows.yaml'), fileSystem);
|
|
8
9
|
const merged = merge(main, workflows === undefined ? {} : normalizeWorkflowFile(workflows));
|
|
9
10
|
try {
|
|
10
11
|
return parseRootConfig(merged);
|
|
@@ -17,12 +18,12 @@ export async function loadConfig(wakeRoot) {
|
|
|
17
18
|
throw error;
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
async function readYaml(path) {
|
|
21
|
-
return YAML.parse(await readFile(path
|
|
21
|
+
async function readYaml(path, fileSystem) {
|
|
22
|
+
return YAML.parse(await fileSystem.readFile(path)) ?? {};
|
|
22
23
|
}
|
|
23
|
-
async function readYamlIfPresent(path) {
|
|
24
|
+
async function readYamlIfPresent(path, fileSystem) {
|
|
24
25
|
try {
|
|
25
|
-
return await readYaml(path);
|
|
26
|
+
return await readYaml(path, fileSystem);
|
|
26
27
|
}
|
|
27
28
|
catch (error) {
|
|
28
29
|
if (error.code === 'ENOENT')
|
|
@@ -2,11 +2,12 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import YAML from 'yaml';
|
|
4
4
|
import { emptyFakeScenarios, parseFakeScenarios, } from '../execution/index.js';
|
|
5
|
-
|
|
5
|
+
const nodeFileReader = { readFile: (path) => readFile(path, 'utf8') };
|
|
6
|
+
export async function loadFakeScenarios(wakeRoot, fileSystem = nodeFileReader) {
|
|
6
7
|
const path = join(wakeRoot, 'fake-scenarios.yaml');
|
|
7
8
|
let source;
|
|
8
9
|
try {
|
|
9
|
-
source = await readFile(path
|
|
10
|
+
source = await fileSystem.readFile(path);
|
|
10
11
|
}
|
|
11
12
|
catch (error) {
|
|
12
13
|
if (error.code === 'ENOENT')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CronExpressionParser } from 'cron-parser';
|
|
1
2
|
export class SchedulePolicy {
|
|
2
3
|
elapsedSlots(config, now, checkpoint) {
|
|
3
4
|
const end = minute(new Date(now));
|
|
@@ -7,13 +8,22 @@ export class SchedulePolicy {
|
|
|
7
8
|
const fields = config.cron.trim().split(/\s+/);
|
|
8
9
|
if (fields.length !== 5)
|
|
9
10
|
throw new Error(`Schedule ${config.id} must use a five-field cron expression`);
|
|
11
|
+
if (fields.some(hasUnsupportedSyntax))
|
|
12
|
+
throw new Error(`Schedule ${config.id} uses unsupported cron syntax`);
|
|
13
|
+
const expression = CronExpressionParser.parse(config.cron, {
|
|
14
|
+
currentDate: new Date(start - 1),
|
|
15
|
+
endDate: new Date(end),
|
|
16
|
+
tz: 'UTC',
|
|
17
|
+
});
|
|
18
|
+
const dayOfMonth = fieldExpression(fields, 2, 4);
|
|
19
|
+
const dayOfWeek = fieldExpression(fields, 4, 2);
|
|
10
20
|
const slots = [];
|
|
11
|
-
|
|
12
|
-
const date =
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
21
|
+
while (expression.hasNext()) {
|
|
22
|
+
const date = expression.next().toDate();
|
|
23
|
+
if (!matchesDays(date, dayOfMonth, dayOfWeek))
|
|
24
|
+
continue;
|
|
25
|
+
const at = date.toISOString();
|
|
26
|
+
slots.push({ identity: `schedule:${config.id}:${at}`, at });
|
|
17
27
|
}
|
|
18
28
|
return slots;
|
|
19
29
|
}
|
|
@@ -23,24 +33,16 @@ function minute(value) {
|
|
|
23
33
|
throw new Error('Schedule timestamps must be valid dates');
|
|
24
34
|
return Math.floor(value.getTime() / 60_000) * 60_000;
|
|
25
35
|
}
|
|
26
|
-
function
|
|
27
|
-
return (
|
|
28
|
-
matchesField(fields[1], date.getUTCHours(), 0, 23) &&
|
|
29
|
-
matchesField(fields[2], date.getUTCDate(), 1, 31) &&
|
|
30
|
-
matchesField(fields[3], date.getUTCMonth() + 1, 1, 12) &&
|
|
31
|
-
matchesField(fields[4], date.getUTCDay(), 0, 6));
|
|
36
|
+
function hasUnsupportedSyntax(field) {
|
|
37
|
+
return /[?#]|(^|[,*/-])H(?=$|[,*/-])|(^|[,*/-])L(?=$|[,*/-])|\dL\b/i.test(field);
|
|
32
38
|
}
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!Number.isInteger(number) || number < minimum || number > maximum)
|
|
43
|
-
throw new Error(`Invalid cron field: ${part}`);
|
|
44
|
-
return value === number;
|
|
45
|
-
});
|
|
39
|
+
function fieldExpression(fields, index, wildcardIndex) {
|
|
40
|
+
if (fields[index] === '*')
|
|
41
|
+
return null;
|
|
42
|
+
const constrained = [...fields];
|
|
43
|
+
constrained[wildcardIndex] = '*';
|
|
44
|
+
return CronExpressionParser.parse(constrained.join(' '), { tz: 'UTC' });
|
|
45
|
+
}
|
|
46
|
+
function matchesDays(date, dayOfMonth, dayOfWeek) {
|
|
47
|
+
return (dayOfMonth?.includesDate(date) ?? true) && (dayOfWeek?.includesDate(date) ?? true);
|
|
46
48
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atolis-hq/wake",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.19",
|
|
4
4
|
"description": "Local autonomous agent control plane for software development",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -34,11 +34,12 @@
|
|
|
34
34
|
"scripts": {
|
|
35
35
|
"build": "tsc -p tsconfig.json && node scripts/embed-version.mjs && node scripts/check-cli-entrypoint.mjs",
|
|
36
36
|
"prepack": "npm run build && npm run build:web",
|
|
37
|
-
"test": "
|
|
37
|
+
"test": "npm run test:unit",
|
|
38
38
|
"test:fast": "npm run test:unit",
|
|
39
39
|
"check:catalogue": "node scripts/check-functional-catalogue.mjs",
|
|
40
40
|
"check:scenarios": "node scripts/check-scenario-coverage.mjs",
|
|
41
41
|
"check:specs": "node scripts/check-specs.mjs",
|
|
42
|
+
"check:specs:report": "node scripts/report-spec-drift.mjs",
|
|
42
43
|
"lint:architecture": "node scripts/check-module-manifests.mjs && node scripts/check-contract-vocabulary.mjs && depcruise --config dependency-cruiser.config.mjs src",
|
|
43
44
|
"build:docker": "tsc -p tsconfig.docker.json && node scripts/embed-version.mjs && npm run build:web",
|
|
44
45
|
"build:web": "npm --workspace @atolis-hq/wake-web run build",
|
|
@@ -62,10 +63,12 @@
|
|
|
62
63
|
"smoke:claude": "tsx src/main.ts smoke claude",
|
|
63
64
|
"smoke:codex": "tsx src/main.ts smoke codex",
|
|
64
65
|
"smoke:cursor": "tsx src/main.ts smoke cursor",
|
|
65
|
-
"verify": "npm run check:catalogue && npm run check:scenarios && npm run lint:architecture && npm run lint && npm run format:check && npm run build && npm test"
|
|
66
|
+
"verify": "npm run check:catalogue && npm run check:scenarios && npm run check:specs:report && npm run lint:architecture && npm run lint && npm run format:check && npm run build && npm run test:unit",
|
|
67
|
+
"verify:ci": "npm run verify && npm run test:architecture && npm run test:integration && npm run test:e2e && npm run knip && npm run test:web"
|
|
66
68
|
},
|
|
67
69
|
"dependencies": {
|
|
68
70
|
"@octokit/rest": "^22.0.0",
|
|
71
|
+
"cron-parser": "^5.10.0",
|
|
69
72
|
"execa": "^10.0.1",
|
|
70
73
|
"handlebars": "^4.7.9",
|
|
71
74
|
"ulid": "^3.0.2",
|