@antelopejs/dms 0.15.1 → 0.16.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/CHANGELOG.md +19 -0
- package/README.md +36 -26
- package/bin/control/base.mjs +9 -4
- package/bin/control/browser-client.mjs +24 -5
- package/bin/control/browser-wire.mjs +41 -0
- package/bin/control/browser-wire.test.mjs +96 -0
- package/bin/control/browser.mjs +78 -27
- package/bin/control/commands-stack.mjs +58 -8
- package/bin/control/commands-verify.mjs +47 -1
- package/bin/control/doctor.mjs +49 -4
- package/bin/control/failure.mjs +9 -0
- package/bin/control/journey.mjs +158 -30
- package/bin/control/journey.test.mjs +61 -0
- package/bin/control/json.mjs +229 -0
- package/bin/control/json.test.mjs +130 -0
- package/bin/control/mail.mjs +57 -7
- package/bin/control/mail.test.mjs +55 -0
- package/bin/control/mongo.mjs +205 -27
- package/bin/control/mongo.test.mjs +298 -0
- package/bin/control/options.mjs +33 -0
- package/bin/control/processes.mjs +24 -2
- package/bin/control/renderer.mjs +39 -0
- package/bin/control/renderer.test.mjs +324 -0
- package/bin/control/run-state.mjs +119 -6
- package/bin/control/run-state.test.mjs +171 -0
- package/bin/control/session.mjs +30 -14
- package/bin/control/stack.mjs +95 -24
- package/bin/control/tab.mjs +117 -18
- package/bin/control/verify/behaviour.mjs +44 -6
- package/bin/control/verify/behaviour.test.mjs +130 -22
- package/bin/control/verify/decode.mjs +220 -0
- package/bin/control/verify/decode.test.mjs +245 -0
- package/bin/control/verify/doors.mjs +1 -1
- package/bin/control/verify/doors.test.mjs +40 -0
- package/bin/control/verify/emit.mjs +135 -9
- package/bin/control/verify/formal.mjs +61 -10
- package/bin/control/verify/formal.test.mjs +19 -3
- package/bin/control/verify/judgement.mjs +22 -3
- package/bin/control/verify/map.mjs +25 -5
- package/bin/control/verify/publish.mjs +20 -14
- package/bin/control/verify/seeded.mjs +65 -16
- package/bin/control/verify/skill.mjs +27 -6
- package/bin/control/verify/types.mjs +29 -0
- package/bin/control/verify/verdict.mjs +72 -7
- package/bin/dms-control.mjs +8 -28
- package/checks/api-error-presentation.d.mts +61 -0
- package/dist/attachments/access.js +1 -1
- package/dist/attachments/access.js.map +1 -1
- package/dist/attachments/fields.d.ts +2 -2
- package/dist/attachments/fields.js +26 -13
- package/dist/attachments/fields.js.map +1 -1
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/attachments/index.js.map +1 -1
- package/dist/attachments/read.js.map +1 -1
- package/dist/attachments/save.d.ts +1 -1
- package/dist/attachments/save.js +49 -17
- package/dist/attachments/save.js.map +1 -1
- package/dist/attachments/table-view.d.ts +1 -2
- package/dist/attachments/table-view.js +23 -13
- package/dist/attachments/table-view.js.map +1 -1
- package/dist/automation/events.d.ts +10 -10
- package/dist/automation/index.js +3 -4
- package/dist/automation/index.js.map +1 -1
- package/dist/automation/send-notification.d.ts +2 -2
- package/dist/automation/send-notification.js +4 -6
- package/dist/automation/send-notification.js.map +1 -1
- package/dist/crons/cleanup-user-invites.js.map +1 -1
- package/dist/crons/sweep-expired-sessions.js.map +1 -1
- package/dist/crons/sweep-stale-exports.js.map +1 -1
- package/dist/db/guard-row.d.ts +2 -3
- package/dist/db/guard-row.js +1 -2
- package/dist/db/guard-row.js.map +1 -1
- package/dist/db/models/attachments.model.d.ts +7 -5
- package/dist/db/models/setupToken.model.d.ts +7 -5
- package/dist/db/models/signInAttempts.model.d.ts +7 -5
- package/dist/db/models/systemState.model.d.ts +7 -5
- package/dist/db/models/userEmailChanges.model.d.ts +7 -5
- package/dist/db/models/userKnownDevices.model.d.ts +7 -5
- package/dist/db/models/userNotificationPreferences.model.d.ts +7 -5
- package/dist/db/models/userNotifications.model.d.ts +9 -6
- package/dist/db/models/userNotifications.model.js.map +1 -1
- package/dist/dev/module-update-notifications.js.map +1 -1
- package/dist/hooks/internal/sign-in-monitor.js +6 -6
- package/dist/hooks/internal/sign-in-monitor.js.map +1 -1
- package/dist/implementations/dms/page/controller.d.ts +2 -2
- package/dist/implementations/dms/page/controller.js +3 -1
- package/dist/implementations/dms/page/controller.js.map +1 -1
- package/dist/implementations/dms/page/dynamic-menu.js +3 -2
- package/dist/implementations/dms/page/dynamic-menu.js.map +1 -1
- package/dist/implementations/dms/page/notifications.d.ts +6 -2
- package/dist/implementations/dms/page/notifications.js +1 -0
- package/dist/implementations/dms/page/notifications.js.map +1 -1
- package/dist/implementations/dms/page/preview-pages.js +6 -3
- package/dist/implementations/dms/page/preview-pages.js.map +1 -1
- package/dist/implementations/dms/page/registry.js +10 -4
- package/dist/implementations/dms/page/registry.js.map +1 -1
- package/dist/implementations/dms/permissions.js +12 -12
- package/dist/implementations/dms/permissions.js.map +1 -1
- package/dist/implementations/dms-base/bulk-selection.d.ts +3 -4
- package/dist/implementations/dms-base/bulk-selection.js +18 -6
- package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
- package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
- package/dist/implementations/dms-base/footer-summary.js +44 -27
- package/dist/implementations/dms-base/footer-summary.js.map +1 -1
- package/dist/implementations/dms-base/row-rule-operand.d.ts +6 -0
- package/dist/implementations/dms-base/row-rule-operand.js +41 -0
- package/dist/implementations/dms-base/row-rule-operand.js.map +1 -0
- package/dist/implementations/dms-base/search-route.d.ts +9 -13
- package/dist/implementations/dms-base/search-route.js +7 -6
- package/dist/implementations/dms-base/search-route.js.map +1 -1
- package/dist/implementations/dms-base/services.d.ts +1 -0
- package/dist/implementations/dms-base/services.js +1 -0
- package/dist/implementations/dms-base/services.js.map +1 -1
- package/dist/implementations/dms-base/table-view.d.ts +5 -6
- package/dist/implementations/dms-base/table-view.js +17 -6
- package/dist/implementations/dms-base/table-view.js.map +1 -1
- package/dist/implementations/dms-html-render/index.js +16 -6
- package/dist/implementations/dms-html-render/index.js.map +1 -1
- package/dist/implementations/dms-notifications/delivery-guard.d.ts +3 -2
- package/dist/implementations/dms-notifications/delivery-guard.js.map +1 -1
- package/dist/implementations/dms-notifications/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/pages/public/preview/block-types.d.ts +1 -2
- package/dist/pages/public/preview/block-types.js.map +1 -1
- package/dist/pages/settings/overview/account-summary.js +4 -1
- package/dist/pages/settings/overview/account-summary.js.map +1 -1
- package/dist/pages/settings/users/account-activity-feed.js +3 -2
- package/dist/pages/settings/users/account-activity-feed.js.map +1 -1
- package/dist/pages/settings/users/account-credentials.d.ts +1 -1
- package/dist/pages/settings/users/account-credentials.js +1 -2
- package/dist/pages/settings/users/account-credentials.js.map +1 -1
- package/dist/pages/settings/users/account-data-store.d.ts +1 -1
- package/dist/pages/settings/users/account-data-store.js +1 -3
- package/dist/pages/settings/users/account-data-store.js.map +1 -1
- package/dist/pages/settings/users/email-change.d.ts +2 -2
- package/dist/pages/settings/users/email-change.js +2 -3
- package/dist/pages/settings/users/email-change.js.map +1 -1
- package/dist/pages/settings/users/invite-extension-routes.d.ts +2 -2
- package/dist/pages/settings/users/invite-extension-routes.js +11 -4
- package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
- package/dist/pages/settings/users/invites.d.ts +2 -2
- package/dist/pages/settings/users/member-invite-batch.d.ts +2 -2
- package/dist/pages/settings/users/member-invite-batch.js +7 -3
- package/dist/pages/settings/users/member-invite-batch.js.map +1 -1
- package/dist/pages/settings/users/member-roles-field.js +1 -4
- package/dist/pages/settings/users/member-roles-field.js.map +1 -1
- package/dist/pages/settings/users/members.d.ts +2 -3
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.d.ts +2 -3
- package/dist/pages/settings/users/notifications.js +2 -1
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/pages/settings/users/profile-helpers.d.ts +0 -1
- package/dist/pages/settings/users/profile-helpers.js +0 -17
- package/dist/pages/settings/users/profile-helpers.js.map +1 -1
- package/dist/pages/settings/users/profile.d.ts +3 -4
- package/dist/pages/settings/users/profile.js +12 -12
- package/dist/pages/settings/users/profile.js.map +1 -1
- package/dist/pages/settings/users/region.d.ts +3 -4
- package/dist/pages/settings/users/region.js.map +1 -1
- package/dist/pages/settings/users/regional-preferences.d.ts +8 -1
- package/dist/pages/settings/users/regional-preferences.js +10 -6
- package/dist/pages/settings/users/regional-preferences.js.map +1 -1
- package/dist/pages/settings/users/role-preview.js +3 -2
- package/dist/pages/settings/users/role-preview.js.map +1 -1
- package/dist/pages/settings/users/roles.d.ts +4 -5
- package/dist/pages/settings/users/roles.js.map +1 -1
- package/dist/pages/settings/users/security.d.ts +9 -12
- package/dist/pages/settings/users/security.js +11 -9
- package/dist/pages/settings/users/security.js.map +1 -1
- package/dist/pages/settings/users/two-factor-operations.d.ts +7 -7
- package/dist/pages/settings/users/two-factor-operations.js +5 -7
- package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
- package/dist/realtime/broker.js.map +1 -1
- package/dist/realtime/heartbeat.js.map +1 -1
- package/dist/realtime/redis-broker.js.map +1 -1
- package/dist/realtime/sessions.js.map +1 -1
- package/dist/realtime/sse.js +1 -2
- package/dist/realtime/sse.js.map +1 -1
- package/dist/routes/auth/forgot-password.d.ts +1 -1
- package/dist/routes/auth/forgot-password.js +1 -3
- package/dist/routes/auth/forgot-password.js.map +1 -1
- package/dist/routes/auth/login.d.ts +1 -1
- package/dist/routes/auth/login.js +1 -2
- package/dist/routes/auth/login.js.map +1 -1
- package/dist/routes/auth/logout.d.ts +1 -1
- package/dist/routes/auth/logout.js +1 -2
- package/dist/routes/auth/logout.js.map +1 -1
- package/dist/routes/auth/oauth/providers.js +30 -6
- package/dist/routes/auth/oauth/providers.js.map +1 -1
- package/dist/routes/auth/oauth/state.js +8 -5
- package/dist/routes/auth/oauth/state.js.map +1 -1
- package/dist/routes/auth/oauth/token-exchange.js +8 -1
- package/dist/routes/auth/oauth/token-exchange.js.map +1 -1
- package/dist/routes/auth/refresh.d.ts +1 -1
- package/dist/routes/auth/refresh.js +1 -2
- package/dist/routes/auth/refresh.js.map +1 -1
- package/dist/routes/auth/request-2fa-email.d.ts +1 -1
- package/dist/routes/auth/request-2fa-email.js +1 -2
- package/dist/routes/auth/request-2fa-email.js.map +1 -1
- package/dist/routes/auth/reset-password.d.ts +1 -1
- package/dist/routes/auth/reset-password.js +1 -3
- package/dist/routes/auth/reset-password.js.map +1 -1
- package/dist/routes/auth/session-handoff.d.ts +1 -1
- package/dist/routes/auth/session-handoff.js +1 -2
- package/dist/routes/auth/session-handoff.js.map +1 -1
- package/dist/routes/auth/signup.d.ts +1 -1
- package/dist/routes/auth/signup.js +1 -2
- package/dist/routes/auth/signup.js.map +1 -1
- package/dist/routes/auth/switch-tenant.d.ts +2 -3
- package/dist/routes/auth/switch-tenant.js +3 -2
- package/dist/routes/auth/switch-tenant.js.map +1 -1
- package/dist/routes/auth/validate-forgot-password-token.d.ts +1 -1
- package/dist/routes/auth/validate-forgot-password-token.js +1 -3
- package/dist/routes/auth/validate-forgot-password-token.js.map +1 -1
- package/dist/routes/auth/validate-invite-token.d.ts +1 -1
- package/dist/routes/auth/validate-invite-token.js +1 -3
- package/dist/routes/auth/validate-invite-token.js.map +1 -1
- package/dist/routes/auth/verify-2fa.d.ts +1 -1
- package/dist/routes/auth/verify-2fa.js +1 -2
- package/dist/routes/auth/verify-2fa.js.map +1 -1
- package/dist/routes/auth/verify-email.d.ts +1 -1
- package/dist/routes/auth/verify-email.js +1 -2
- package/dist/routes/auth/verify-email.js.map +1 -1
- package/dist/routes/auth.d.ts +14 -15
- package/dist/routes/auth.js +15 -13
- package/dist/routes/auth.js.map +1 -1
- package/dist/routes/files.d.ts +1 -2
- package/dist/routes/files.js.map +1 -1
- package/dist/routes/onboarding.d.ts +1 -2
- package/dist/routes/onboarding.js.map +1 -1
- package/dist/routes/realtime.js +5 -6
- package/dist/routes/realtime.js.map +1 -1
- package/dist/routes/store.d.ts +6 -7
- package/dist/routes/store.js +19 -13
- package/dist/routes/store.js.map +1 -1
- package/dist/store/catalog.d.ts +13 -5
- package/dist/store/catalog.js +19 -9
- package/dist/store/catalog.js.map +1 -1
- package/dist/store/compatibility.d.ts +1 -0
- package/dist/store/compatibility.js +24 -8
- package/dist/store/compatibility.js.map +1 -1
- package/dist/store/frontend-compatibility.js +15 -3
- package/dist/store/frontend-compatibility.js.map +1 -1
- package/dist/store/interfaces.js +35 -13
- package/dist/store/interfaces.js.map +1 -1
- package/dist/store/internal/frontend/frontend-modules.js +7 -3
- package/dist/store/internal/frontend/frontend-modules.js.map +1 -1
- package/dist/store/internal/frontend/frontend-ready.d.ts +2 -1
- package/dist/store/internal/frontend/frontend-ready.js +6 -4
- package/dist/store/internal/frontend/frontend-ready.js.map +1 -1
- package/dist/store/internal/frontend/manifest.js +6 -7
- package/dist/store/internal/frontend/manifest.js.map +1 -1
- package/dist/store/operations.js.map +1 -1
- package/dist/store/registry.js.map +1 -1
- package/dist/store/runtime.d.ts +13 -0
- package/dist/store/runtime.js +9 -1
- package/dist/store/runtime.js.map +1 -1
- package/dist/utils/admin-invite-email.js +3 -1
- package/dist/utils/admin-invite-email.js.map +1 -1
- package/dist/utils/email-subjects.js +3 -1
- package/dist/utils/email-subjects.js.map +1 -1
- package/dist/utils/export-jobs-run.js.map +1 -1
- package/dist/utils/export-jobs.js +3 -9
- package/dist/utils/export-jobs.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/own-value.d.ts +1 -0
- package/dist/utils/own-value.js +7 -0
- package/dist/utils/own-value.js.map +1 -0
- package/dist/utils/row-action-rule-evaluator.js +31 -35
- package/dist/utils/row-action-rule-evaluator.js.map +1 -1
- package/dist/utils/sign-in-country.js +4 -2
- package/dist/utils/sign-in-country.js.map +1 -1
- package/dist/utils/tenant-export-archive.js +9 -14
- package/dist/utils/tenant-export-archive.js.map +1 -1
- package/dist/utils/user-agent.js +2 -1
- package/dist/utils/user-agent.js.map +1 -1
- package/dist/validation/auth.schema.d.ts +10 -10
- package/dist/validation/regional-preferences.schema.d.ts +2 -2
- package/dist/validation/role-editor.schema.d.ts +1 -1
- package/frontend-vue/build/backend-boundary.ts +49 -18
- package/frontend-vue/dms.frontend.ts +8 -12
- package/frontend-vue/layers/dms-auth/server/accounts.ts +44 -3
- package/frontend-vue/layers/dms-auth/server/actions.ts +17 -4
- package/frontend-vue/layers/dms-auth/server/oauth.ts +42 -4
- package/frontend-vue/layers/dms-core/app/types/watch.ts +1 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +57 -6
- package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +7 -9
- package/frontend-vue/layers/dms-ui/app/entries/composables/form/registerFormFunctions.ts +1 -0
- package/frontend-vue/lazy-plugin.ts +18 -0
- package/frontend-vue/package.json +2 -2
- package/package.json +19 -13
- package/skills/dms-project/SKILL.md +4 -3
|
@@ -2,9 +2,15 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join, matchesGlob, relative } from "node:path";
|
|
4
4
|
import { behaviourChange, inWorktree } from "./behaviour.mjs";
|
|
5
|
+
import { decodeFormalResult } from "./decode.mjs";
|
|
5
6
|
|
|
6
7
|
const OUTPUT_LIMIT = 64 * 1024 * 1024;
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @param {import("./types.mjs").FormalCheck} check The check
|
|
11
|
+
* @param {string[]} changed Repository paths
|
|
12
|
+
* @returns {boolean} Whether a changed path matches the check's own files
|
|
13
|
+
*/
|
|
8
14
|
function changesCheckFiles(check, changed) {
|
|
9
15
|
return changed.some((file) =>
|
|
10
16
|
(check.files ?? []).some((glob) => matchesGlob(file, glob)),
|
|
@@ -14,9 +20,9 @@ function changesCheckFiles(check, changed) {
|
|
|
14
20
|
/**
|
|
15
21
|
* The formal checks whose covered files or own `files` a set of changed files touches.
|
|
16
22
|
*
|
|
17
|
-
* @param {
|
|
23
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
18
24
|
* @param {string[]} changed Repository paths
|
|
19
|
-
* @returns {
|
|
25
|
+
* @returns {import("./types.mjs").FormalCheck[]} The checks
|
|
20
26
|
*/
|
|
21
27
|
export function touchedFormalChecks(skill, changed) {
|
|
22
28
|
return skill.config.formal.filter(
|
|
@@ -26,6 +32,12 @@ export function touchedFormalChecks(skill, changed) {
|
|
|
26
32
|
);
|
|
27
33
|
}
|
|
28
34
|
|
|
35
|
+
/**
|
|
36
|
+
* @param {import("./types.mjs").FormalCheck} check The check
|
|
37
|
+
* @param {number | null} exitCode How the run exited
|
|
38
|
+
* @param {string} output What the run printed
|
|
39
|
+
* @returns {string[]} The violated rules
|
|
40
|
+
*/
|
|
29
41
|
function violatedRules(check, exitCode, output) {
|
|
30
42
|
if (check.violation) {
|
|
31
43
|
return [
|
|
@@ -37,6 +49,11 @@ function violatedRules(check, exitCode, output) {
|
|
|
37
49
|
return exitCode === 0 ? [] : ["property"];
|
|
38
50
|
}
|
|
39
51
|
|
|
52
|
+
/**
|
|
53
|
+
* @param {string[]} command The program and its arguments
|
|
54
|
+
* @param {string} cwd Where to run it
|
|
55
|
+
* @returns {import("node:child_process").SpawnSyncReturns<string>} The run
|
|
56
|
+
*/
|
|
40
57
|
function runCommand(command, cwd) {
|
|
41
58
|
const [program, ...args] = command;
|
|
42
59
|
return spawnSync(program, args, {
|
|
@@ -46,6 +63,12 @@ function runCommand(command, cwd) {
|
|
|
46
63
|
});
|
|
47
64
|
}
|
|
48
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} folder The evidence folder
|
|
68
|
+
* @param {string} prefix `base-` for the base run, else empty
|
|
69
|
+
* @param {string[]} command The command that ran
|
|
70
|
+
* @param {import("node:child_process").SpawnSyncReturns<string>} run The run
|
|
71
|
+
*/
|
|
49
72
|
function writeRun(folder, prefix, command, run) {
|
|
50
73
|
writeFileSync(join(folder, `${prefix}command.txt`), `${command.join(" ")}\n`);
|
|
51
74
|
writeFileSync(join(folder, `${prefix}stdout.txt`), run.stdout ?? "");
|
|
@@ -56,12 +79,22 @@ function writeRun(folder, prefix, command, run) {
|
|
|
56
79
|
writeFileSync(join(folder, `${prefix}exit-code.txt`), `${run.status}\n`);
|
|
57
80
|
}
|
|
58
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @param {import("./types.mjs").FormalCheck} check The check
|
|
84
|
+
* @param {string} output What the run printed
|
|
85
|
+
* @returns {string[]} The passing rules
|
|
86
|
+
*/
|
|
59
87
|
function passingRules(check, output) {
|
|
60
88
|
return check.passing
|
|
61
89
|
? [...new Set([...output.matchAll(check.passing)].map((match) => match[1]))]
|
|
62
90
|
: [];
|
|
63
91
|
}
|
|
64
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @param {import("./types.mjs").FormalCheck} check The check
|
|
95
|
+
* @param {import("node:child_process").SpawnSyncReturns<string>} run The run
|
|
96
|
+
* @returns {import("./types.mjs").Rules} The exit code and rules
|
|
97
|
+
*/
|
|
65
98
|
function rulesOf(check, run) {
|
|
66
99
|
return {
|
|
67
100
|
exitCode: run.status,
|
|
@@ -70,6 +103,13 @@ function rulesOf(check, run) {
|
|
|
70
103
|
};
|
|
71
104
|
}
|
|
72
105
|
|
|
106
|
+
/**
|
|
107
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
108
|
+
* @param {import("./types.mjs").FormalCheck} check The check
|
|
109
|
+
* @param {string} baseSha The base commit
|
|
110
|
+
* @param {string} folder The evidence folder
|
|
111
|
+
* @returns {import("./types.mjs").Rules} The rules at the base
|
|
112
|
+
*/
|
|
73
113
|
function baseRules(skill, check, baseSha, folder) {
|
|
74
114
|
const command = check.baseCommand ?? check.command;
|
|
75
115
|
const run = inWorktree(skill.repo, baseSha, (dir) =>
|
|
@@ -79,6 +119,14 @@ function baseRules(skill, check, baseSha, folder) {
|
|
|
79
119
|
return rulesOf(check, run);
|
|
80
120
|
}
|
|
81
121
|
|
|
122
|
+
/**
|
|
123
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
124
|
+
* @param {import("./types.mjs").FormalCheck} check The check
|
|
125
|
+
* @param {{ baseSha: string, headSha: string, changed: string[] }} diff The diff
|
|
126
|
+
* @param {string} folder The evidence folder
|
|
127
|
+
* @param {import("./types.mjs").Rules} head The rules at HEAD
|
|
128
|
+
* @returns {Pick<import("./types.mjs").FormalResult, "behaviour" | "checkChanged" | "base" | "fixed" | "missing">} The gate fields
|
|
129
|
+
*/
|
|
82
130
|
function gateFields(skill, check, diff, folder, head) {
|
|
83
131
|
const behaviour = behaviourChange(skill, check, diff);
|
|
84
132
|
const checkChanged = changesCheckFiles(check, diff.changed);
|
|
@@ -106,11 +154,11 @@ function gateFields(skill, check, diff, folder, head) {
|
|
|
106
154
|
* `fixed` lists the rules the base broke that HEAD reports passing, and
|
|
107
155
|
* `missing` the rules the base broke that HEAD reports neither failing nor passing.
|
|
108
156
|
*
|
|
109
|
-
* @param {
|
|
110
|
-
* @param {
|
|
111
|
-
* @param {
|
|
157
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
158
|
+
* @param {import("../run-state.mjs").RunState} state The run
|
|
159
|
+
* @param {import("./types.mjs").FormalCheck} check The check
|
|
112
160
|
* @param {{ baseSha: string, headSha: string, changed: string[] } | null} diff The diff's base, head and changed files, for a check that covers it
|
|
113
|
-
* @returns {
|
|
161
|
+
* @returns {import("./types.mjs").FormalResult} The result
|
|
114
162
|
*/
|
|
115
163
|
export function runFormalCheck(skill, state, check, diff) {
|
|
116
164
|
const folder = join(state.evidence, "formal", check.name);
|
|
@@ -147,7 +195,7 @@ export function runFormalCheck(skill, state, check, diff) {
|
|
|
147
195
|
* covered code's behaviour; a rule the base reported and HEAD no longer
|
|
148
196
|
* reports fails; rules the base broke that HEAD reports passing are fixed.
|
|
149
197
|
*
|
|
150
|
-
* @param {
|
|
198
|
+
* @param {import("./types.mjs").FormalResult} result The recorded result
|
|
151
199
|
* @returns {{ fails: boolean, outcome: string }} The weight and its wording
|
|
152
200
|
*/
|
|
153
201
|
export function formalGate(result) {
|
|
@@ -198,11 +246,14 @@ export function formalGate(result) {
|
|
|
198
246
|
/**
|
|
199
247
|
* The recorded result of a formal check in a run, if it ran.
|
|
200
248
|
*
|
|
201
|
-
* @param {
|
|
249
|
+
* @param {import("../run-state.mjs").RunState} state The run
|
|
202
250
|
* @param {string} name The check
|
|
203
|
-
* @returns {
|
|
251
|
+
* @returns {import("./types.mjs").FormalResult | undefined} The result
|
|
204
252
|
*/
|
|
205
253
|
export function readFormalResult(state, name) {
|
|
206
254
|
const file = join(state.evidence, "formal", name, "result.json");
|
|
207
|
-
|
|
255
|
+
if (!existsSync(file)) return undefined;
|
|
256
|
+
/** @type {unknown} */
|
|
257
|
+
const stored = JSON.parse(readFileSync(file, "utf8"));
|
|
258
|
+
return decodeFormalResult(stored, file);
|
|
208
259
|
}
|
|
@@ -28,10 +28,19 @@ const CHECK_CONFIG = {
|
|
|
28
28
|
behaviour: { kind: "emit" },
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} repo
|
|
33
|
+
* @param {...string} args
|
|
34
|
+
*/
|
|
31
35
|
function git(repo, ...args) {
|
|
32
36
|
return execFileSync("git", args, { cwd: repo, encoding: "utf8" }).trim();
|
|
33
37
|
}
|
|
34
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @param {string} repo
|
|
41
|
+
* @param {Record<string, string>} files
|
|
42
|
+
* @param {string} message
|
|
43
|
+
*/
|
|
35
44
|
function commit(repo, files, message) {
|
|
36
45
|
for (const [path, content] of Object.entries(files)) {
|
|
37
46
|
mkdirSync(dirname(join(repo, path)), { recursive: true });
|
|
@@ -54,6 +63,10 @@ function commit(repo, files, message) {
|
|
|
54
63
|
return git(repo, "rev-parse", "HEAD");
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
/**
|
|
67
|
+
* @param {import("node:test").TestContext} t
|
|
68
|
+
* @param {Record<string, string>} head
|
|
69
|
+
*/
|
|
57
70
|
function formalRepo(t, head) {
|
|
58
71
|
const root = mkdtempSync(join(tmpdir(), "dms-control-formal-test-"));
|
|
59
72
|
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
@@ -77,12 +90,15 @@ function formalRepo(t, head) {
|
|
|
77
90
|
return { skill, state: { evidence }, diff: { baseSha, headSha, changed } };
|
|
78
91
|
}
|
|
79
92
|
|
|
93
|
+
/**
|
|
94
|
+
* @param {ReturnType<typeof formalRepo>} repo
|
|
95
|
+
*/
|
|
80
96
|
function gated({ skill, state, diff }) {
|
|
81
97
|
const [check] = touchedFormalChecks(skill, diff.changed);
|
|
82
98
|
return check ? runFormalCheck(skill, state, check, diff) : null;
|
|
83
99
|
}
|
|
84
100
|
|
|
85
|
-
test("a rule counts as fixed only when HEAD reports it passing, never when HEAD drops it", (t) => {
|
|
101
|
+
await test("a rule counts as fixed only when HEAD reports it passing, never when HEAD drops it", (t) => {
|
|
86
102
|
const fixed = gated(
|
|
87
103
|
formalRepo(t, {
|
|
88
104
|
"src/math.js": ADD.replace("a + b", "a + b + 0"),
|
|
@@ -101,7 +117,7 @@ test("a rule counts as fixed only when HEAD reports it passing, never when HEAD
|
|
|
101
117
|
assert.deepEqual(dropped.missing, ["NegativeZero"]);
|
|
102
118
|
});
|
|
103
119
|
|
|
104
|
-
test("a diff that touches only a check's own files runs the check and gates it against the base", (t) => {
|
|
120
|
+
await test("a diff that touches only a check's own files runs the check and gates it against the base", (t) => {
|
|
105
121
|
const result = gated(
|
|
106
122
|
formalRepo(t, {
|
|
107
123
|
".agents/skills/formal-add/check.mjs": CHECK_WITHOUT_RULE,
|
|
@@ -114,7 +130,7 @@ test("a diff that touches only a check's own files runs the check and gates it a
|
|
|
114
130
|
});
|
|
115
131
|
});
|
|
116
132
|
|
|
117
|
-
test("a diff that touches neither the covered files nor the check's files does not touch the check", (t) => {
|
|
133
|
+
await test("a diff that touches neither the covered files nor the check's files does not touch the check", (t) => {
|
|
118
134
|
const { skill, diff } = formalRepo(t, { "README.md": "# Demo\n" });
|
|
119
135
|
assert.deepEqual(touchedFormalChecks(skill, diff.changed), []);
|
|
120
136
|
});
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { decodeRawJudgement } from "./decode.mjs";
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
6
|
+
* @param {string | undefined} value The kind a judgement names
|
|
7
|
+
* @param {string} label Where the kind was read
|
|
8
|
+
* @returns {string} The kind
|
|
9
|
+
*/
|
|
3
10
|
function doorKind(skill, value, label) {
|
|
4
11
|
if (!Object.hasOwn(skill.config.doors, value)) {
|
|
5
12
|
throw new Error(
|
|
@@ -9,6 +16,11 @@ function doorKind(skill, value, label) {
|
|
|
9
16
|
return value;
|
|
10
17
|
}
|
|
11
18
|
|
|
19
|
+
/**
|
|
20
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
21
|
+
* @param {import("./types.mjs").RawJudgement} raw The judgement file
|
|
22
|
+
* @returns {import("./types.mjs").DoorRow[]} The door rows
|
|
23
|
+
*/
|
|
12
24
|
function parseRows(skill, raw) {
|
|
13
25
|
return (raw.doors?.rows ?? []).map((row, index) => {
|
|
14
26
|
if (!row.kind || !row.location || !row.irreversible || !row.containment) {
|
|
@@ -24,6 +36,11 @@ function parseRows(skill, raw) {
|
|
|
24
36
|
});
|
|
25
37
|
}
|
|
26
38
|
|
|
39
|
+
/**
|
|
40
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
41
|
+
* @param {import("./types.mjs").RawJudgement} raw The judgement file
|
|
42
|
+
* @returns {import("./types.mjs").FalsePositive[]} The dismissed doors
|
|
43
|
+
*/
|
|
27
44
|
function parseFalsePositives(skill, raw) {
|
|
28
45
|
return (raw.doors?.falsePositives ?? []).map((entry, index) => {
|
|
29
46
|
if (!entry.location || !entry.reason?.trim()) {
|
|
@@ -43,9 +60,9 @@ function parseFalsePositives(skill, raw) {
|
|
|
43
60
|
* verdict without a One-way doors section is void, and one that does not
|
|
44
61
|
* name its verifier is refused, so both throw.
|
|
45
62
|
*
|
|
46
|
-
* @param {
|
|
63
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
47
64
|
* @param {string} file The judgement file
|
|
48
|
-
* @returns {
|
|
65
|
+
* @returns {import("./types.mjs").Judgement} The judgement
|
|
49
66
|
*/
|
|
50
67
|
export function readJudgement(skill, file) {
|
|
51
68
|
if (!existsSync(file)) {
|
|
@@ -53,7 +70,9 @@ export function readJudgement(skill, file) {
|
|
|
53
70
|
`${file} is missing: write the verifier's judgement before rendering the verdict`,
|
|
54
71
|
);
|
|
55
72
|
}
|
|
56
|
-
|
|
73
|
+
/** @type {unknown} */
|
|
74
|
+
const stored = JSON.parse(readFileSync(file, "utf8"));
|
|
75
|
+
const raw = decodeRawJudgement(stored, file);
|
|
57
76
|
const rows = parseRows(skill, raw);
|
|
58
77
|
const none = raw.doors?.none?.trim() || null;
|
|
59
78
|
if (!none && rows.length === 0) {
|
|
@@ -5,6 +5,11 @@ import { flaggedDoors, patchFiles } from "./doors.mjs";
|
|
|
5
5
|
import { loadJourneys } from "../journey.mjs";
|
|
6
6
|
import { journeysFile, readFeatures } from "./skill.mjs";
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* @param {string} file A repository path
|
|
10
|
+
* @param {string[]} globs The globs
|
|
11
|
+
* @returns {boolean} Whether a glob matches the path
|
|
12
|
+
*/
|
|
8
13
|
function matches(file, globs) {
|
|
9
14
|
return globs.some((glob) => matchesGlob(file, glob));
|
|
10
15
|
}
|
|
@@ -13,7 +18,7 @@ function matches(file, globs) {
|
|
|
13
18
|
* Whether a repository path is user-facing: matched by the config's
|
|
14
19
|
* `userFacing` globs and by none of its `notUserFacing` globs.
|
|
15
20
|
*
|
|
16
|
-
* @param {
|
|
21
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
17
22
|
* @param {string} file A repository path
|
|
18
23
|
* @returns {boolean} True when a feature must cover it
|
|
19
24
|
*/
|
|
@@ -37,10 +42,20 @@ export function featuresOf(file, features) {
|
|
|
37
42
|
.map((feature) => feature.name);
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
/**
|
|
46
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
47
|
+
* @returns {string[]} The tracked paths
|
|
48
|
+
*/
|
|
40
49
|
function trackedFiles(skill) {
|
|
41
50
|
return git(["ls-files"], skill.repo).split("\n").filter(Boolean);
|
|
42
51
|
}
|
|
43
52
|
|
|
53
|
+
/**
|
|
54
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
55
|
+
* @param {ReturnType<typeof readFeatures>[number]} feature The feature
|
|
56
|
+
* @param {string[]} tracked The tracked paths
|
|
57
|
+
* @returns {Promise<string[]>} One line per problem
|
|
58
|
+
*/
|
|
44
59
|
async function featureProblems(skill, feature, tracked) {
|
|
45
60
|
const problems = [];
|
|
46
61
|
const file = journeysFile(skill, feature.name);
|
|
@@ -84,6 +99,11 @@ async function featureProblems(skill, feature, tracked) {
|
|
|
84
99
|
return problems;
|
|
85
100
|
}
|
|
86
101
|
|
|
102
|
+
/**
|
|
103
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
104
|
+
* @param {string[]} tracked The tracked paths
|
|
105
|
+
* @returns {string[]} One line per problem
|
|
106
|
+
*/
|
|
87
107
|
function doorProblems(skill, tracked) {
|
|
88
108
|
const problems = [];
|
|
89
109
|
for (const [kind, rule] of Object.entries(skill.config.doors)) {
|
|
@@ -112,7 +132,7 @@ function doorProblems(skill, tracked) {
|
|
|
112
132
|
* pages, every feature and door glob
|
|
113
133
|
* matches a tracked file, and every door rule has a seeded door.
|
|
114
134
|
*
|
|
115
|
-
* @param {
|
|
135
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
116
136
|
* @returns {Promise<string[]>} One line per problem
|
|
117
137
|
*/
|
|
118
138
|
export async function checkMap(skill) {
|
|
@@ -146,7 +166,7 @@ const CONTAINED_KINDS = ["data", "format"];
|
|
|
146
166
|
* doors a set of changed files opens: a door row must name one of them,
|
|
147
167
|
* passing in the run.
|
|
148
168
|
*
|
|
149
|
-
* @param {
|
|
169
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
150
170
|
* @param {string[]} changed Repository paths
|
|
151
171
|
* @returns {string[]} The journeys, each once
|
|
152
172
|
*/
|
|
@@ -167,9 +187,9 @@ function containmentJourneys(skill, changed) {
|
|
|
167
187
|
* outside every glob is an unmapped surface, and `containments` lists the
|
|
168
188
|
* journeys that can contain each Data or Format door the diff opens.
|
|
169
189
|
*
|
|
170
|
-
* @param {
|
|
190
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
171
191
|
* @param {string} baseRef The base, such as `origin/main`
|
|
172
|
-
* @returns {
|
|
192
|
+
* @returns {import("./types.mjs").Plan} The plan
|
|
173
193
|
*/
|
|
174
194
|
export function planFor(skill, baseRef) {
|
|
175
195
|
const features = readFeatures(skill);
|
|
@@ -3,11 +3,16 @@ import { cpSync, mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { git } from "../git.mjs";
|
|
6
|
+
import { decodeCommitStatuses } from "./decode.mjs";
|
|
6
7
|
import { originRepository } from "./verdict.mjs";
|
|
7
8
|
|
|
8
9
|
const EVIDENCE_BRANCH = "verify-evidence";
|
|
9
10
|
const SCRATCH_BRANCH = "verify-evidence-scratch";
|
|
10
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @param {string} repo The checkout
|
|
14
|
+
* @returns {boolean} Whether origin holds the evidence branch
|
|
15
|
+
*/
|
|
11
16
|
function remoteBranchExists(repo) {
|
|
12
17
|
return git(["ls-remote", "--heads", "origin", EVIDENCE_BRANCH], repo) !== "";
|
|
13
18
|
}
|
|
@@ -17,8 +22,8 @@ function remoteBranchExists(repo) {
|
|
|
17
22
|
* `verify-evidence` branch and pushes it, from a temporary worktree so the
|
|
18
23
|
* checkout under review is never touched.
|
|
19
24
|
*
|
|
20
|
-
* @param {
|
|
21
|
-
* @param {
|
|
25
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
26
|
+
* @param {import("../run-state.mjs").RunState} state The run
|
|
22
27
|
* @param {{ pr: number, headSha: string, result: string }} verdict The verdict
|
|
23
28
|
* @returns {string} The pushed evidence commit
|
|
24
29
|
*/
|
|
@@ -102,7 +107,7 @@ export function pushEvidence(skill, state, verdict) {
|
|
|
102
107
|
/**
|
|
103
108
|
* Posts the verdict as a PR comment.
|
|
104
109
|
*
|
|
105
|
-
* @param {
|
|
110
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
106
111
|
* @param {{ pr: number, markdown: string }} verdict The verdict
|
|
107
112
|
* @returns {string} The comment URL gh prints
|
|
108
113
|
*/
|
|
@@ -126,7 +131,7 @@ export function postComment(skill, verdict) {
|
|
|
126
131
|
* Sets the `verify` commit status on the head SHA the verdict judged:
|
|
127
132
|
* `success` for PASS and PASS+NOTES, `failure` for FAIL.
|
|
128
133
|
*
|
|
129
|
-
* @param {
|
|
134
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
130
135
|
* @param {{ headSha: string, result: string }} verdict The verdict
|
|
131
136
|
* @param {string} targetUrl The comment URL
|
|
132
137
|
* @returns {string | null} GitHub's error when the status could not be set
|
|
@@ -159,21 +164,22 @@ export function setStatus(skill, verdict, targetUrl) {
|
|
|
159
164
|
* Reads back the `verify` status GitHub holds for a SHA, so the write is
|
|
160
165
|
* checked through a second command.
|
|
161
166
|
*
|
|
162
|
-
* @param {
|
|
167
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
163
168
|
* @param {string} sha The commit
|
|
164
169
|
* @returns {string} The status state, or `absent`
|
|
165
170
|
*/
|
|
166
171
|
export function readStatus(skill, sha) {
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
},
|
|
175
|
-
),
|
|
172
|
+
const answer = execFileSync(
|
|
173
|
+
"gh",
|
|
174
|
+
["api", `repos/${originRepository(skill.repo)}/commits/${sha}/statuses`],
|
|
175
|
+
{
|
|
176
|
+
cwd: skill.repo,
|
|
177
|
+
encoding: "utf8",
|
|
178
|
+
},
|
|
176
179
|
);
|
|
180
|
+
/** @type {unknown} */
|
|
181
|
+
const stored = JSON.parse(answer);
|
|
182
|
+
const statuses = decodeCommitStatuses(stored, "gh api statuses");
|
|
177
183
|
return (
|
|
178
184
|
statuses.find((status) => status.context === "verify")?.state ?? "absent"
|
|
179
185
|
);
|
|
@@ -3,18 +3,24 @@ import {
|
|
|
3
3
|
cpSync,
|
|
4
4
|
existsSync,
|
|
5
5
|
mkdtempSync,
|
|
6
|
-
readFileSync,
|
|
7
6
|
rmSync,
|
|
8
7
|
writeFileSync,
|
|
9
8
|
} from "node:fs";
|
|
10
9
|
import { tmpdir } from "node:os";
|
|
11
|
-
import { join, relative } from "node:path";
|
|
10
|
+
import { join, relative, resolve } from "node:path";
|
|
12
11
|
import { git } from "../git.mjs";
|
|
13
12
|
import { CONTROL_BIN } from "../stack.mjs";
|
|
13
|
+
import { readFeatureResults } from "./decode.mjs";
|
|
14
14
|
import { runPrepare } from "./skill.mjs";
|
|
15
15
|
|
|
16
16
|
const OUTPUT_LIMIT = 64 * 1024 * 1024;
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} cwd Where to run it
|
|
20
|
+
* @param {string} program The program
|
|
21
|
+
* @param {string[]} args Its arguments
|
|
22
|
+
* @returns {import("node:child_process").SpawnSyncReturns<string>} The run
|
|
23
|
+
*/
|
|
18
24
|
function run(cwd, program, args) {
|
|
19
25
|
return spawnSync(program, args, {
|
|
20
26
|
cwd,
|
|
@@ -24,6 +30,12 @@ function run(cwd, program, args) {
|
|
|
24
30
|
});
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} cwd Where to run it
|
|
35
|
+
* @param {string} program The program
|
|
36
|
+
* @param {string[]} args Its arguments
|
|
37
|
+
* @returns {string} Its trimmed output
|
|
38
|
+
*/
|
|
27
39
|
function mustRun(cwd, program, args) {
|
|
28
40
|
const result = run(cwd, program, args);
|
|
29
41
|
if (result.status !== 0) {
|
|
@@ -36,9 +48,11 @@ function mustRun(cwd, program, args) {
|
|
|
36
48
|
|
|
37
49
|
/**
|
|
38
50
|
* A scratch worktree of HEAD with the current skill copied in, the patch
|
|
39
|
-
* applied when there is one, and the config's `prepare` commands run.
|
|
51
|
+
* applied when there is one, and the config's `prepare` commands run. The
|
|
52
|
+
* worktree is the skill's repository, which is also the renderer checkout
|
|
53
|
+
* when the config names a `renderer`: the patch changes what the run serves.
|
|
40
54
|
*
|
|
41
|
-
* @param {
|
|
55
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
42
56
|
* @param {string} label What the worktree is for
|
|
43
57
|
* @param {string | null} patch The patch file, or `null` for the control
|
|
44
58
|
* @returns {{ worktree: string, skill: string }} The worktree and its skill copy
|
|
@@ -56,21 +70,48 @@ function prepareWorktree(skill, label, patch) {
|
|
|
56
70
|
return { worktree, skill: copy };
|
|
57
71
|
}
|
|
58
72
|
|
|
73
|
+
/**
|
|
74
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
75
|
+
* @param {string} worktree The scratch worktree
|
|
76
|
+
*/
|
|
59
77
|
function removeWorktree(skill, worktree) {
|
|
60
78
|
git(["worktree", "remove", "--force", worktree], skill.repo);
|
|
61
79
|
}
|
|
62
80
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
/**
|
|
82
|
+
* The `up` arguments for a scratch worktree: the config's playground and
|
|
83
|
+
* renderer, resolved in the worktree unless absolute.
|
|
84
|
+
*
|
|
85
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
86
|
+
* @param {{ worktree: string, skill: string }} prepared The scratch worktree and its skill copy
|
|
87
|
+
* @returns {string[]} The arguments
|
|
88
|
+
*/
|
|
89
|
+
export function upArguments(skill, prepared) {
|
|
90
|
+
const { playground, renderer } = skill.config;
|
|
91
|
+
return [
|
|
68
92
|
"up",
|
|
69
93
|
"--playground",
|
|
70
|
-
playground,
|
|
94
|
+
resolve(prepared.worktree, playground),
|
|
95
|
+
...(renderer ? ["--renderer", resolve(prepared.worktree, renderer)] : []),
|
|
71
96
|
"--skill",
|
|
72
97
|
prepared.skill,
|
|
73
|
-
]
|
|
98
|
+
];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {{ worktree: string, skill: string }} prepared The scratch worktree and its skill copy
|
|
103
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
104
|
+
* @param {string} feature The feature
|
|
105
|
+
* @returns {{ run: string, results: import("./types.mjs").FeatureResult[], output: string }} The run folder, its results and output
|
|
106
|
+
*/
|
|
107
|
+
function runFeatureIn(prepared, skill, feature) {
|
|
108
|
+
/**
|
|
109
|
+
* @param {string[]} args The dms-control arguments
|
|
110
|
+
* @returns {import("node:child_process").SpawnSyncReturns<string>} The run
|
|
111
|
+
*/
|
|
112
|
+
const control = (args) =>
|
|
113
|
+
run(prepared.worktree, process.execPath, [CONTROL_BIN, ...args]);
|
|
114
|
+
const started = control(upArguments(skill, prepared));
|
|
74
115
|
const dir = started.stdout.trim().split("\n").at(-1) ?? "";
|
|
75
116
|
if (started.status !== 0 || !existsSync(join(dir, "run.json"))) {
|
|
76
117
|
return {
|
|
@@ -88,16 +129,24 @@ function runFeatureIn(prepared, skill, feature) {
|
|
|
88
129
|
]);
|
|
89
130
|
control(["down", dir]);
|
|
90
131
|
const file = join(dir, "evidence", feature, "feature.json");
|
|
91
|
-
const results =
|
|
92
|
-
? JSON.parse(readFileSync(file, "utf8"))
|
|
93
|
-
: [];
|
|
132
|
+
const results = readFeatureResults(file);
|
|
94
133
|
return { run: dir, results, output: `${journeys.stdout}${journeys.stderr}` };
|
|
95
134
|
}
|
|
96
135
|
|
|
136
|
+
/**
|
|
137
|
+
* @param {import("./types.mjs").FeatureResult} result A journey result
|
|
138
|
+
* @returns {boolean} Whether one of its checks failed
|
|
139
|
+
*/
|
|
97
140
|
function failsACheck(result) {
|
|
98
141
|
return result.steps.some((step) => step.checks.some((check) => !check.pass));
|
|
99
142
|
}
|
|
100
143
|
|
|
144
|
+
/**
|
|
145
|
+
* @param {string} feature The feature
|
|
146
|
+
* @param {ReturnType<typeof runFeatureIn>} control The unpatched run
|
|
147
|
+
* @param {ReturnType<typeof runFeatureIn>} seeded The patched run
|
|
148
|
+
* @returns {import("./types.mjs").SeededOutcome} The outcome
|
|
149
|
+
*/
|
|
101
150
|
function judge(feature, control, seeded) {
|
|
102
151
|
const passedInControl = new Set(
|
|
103
152
|
control.results
|
|
@@ -134,9 +183,9 @@ function judge(feature, control, seeded) {
|
|
|
134
183
|
* harness error and does not count. Worktrees are removed; both runs keep
|
|
135
184
|
* their evidence, and `seeded.json` in the patched run records the outcome.
|
|
136
185
|
*
|
|
137
|
-
* @param {
|
|
186
|
+
* @param {import("./skill.mjs").Skill} skill The skill
|
|
138
187
|
* @param {string[]} features The features
|
|
139
|
-
* @returns {
|
|
188
|
+
* @returns {import("./types.mjs").SeededOutcome[]} One outcome per feature
|
|
140
189
|
*/
|
|
141
190
|
export function runSeededBreaks(skill, features) {
|
|
142
191
|
const control = prepareWorktree(skill, "control", null);
|
|
@@ -3,13 +3,26 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
3
3
|
import { basename, join, relative, resolve } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { checkoutOf } from "../git.mjs";
|
|
6
|
+
import { isRecord } from "../json.mjs";
|
|
7
|
+
import { decodeSkillConfig } from "./decode.mjs";
|
|
6
8
|
|
|
7
9
|
const CONFIG_FILE = "verify.config.ts";
|
|
8
10
|
const OUTPUT_LIMIT = 64 * 1024 * 1024;
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {{ dir: string, name: string, repo: string, config: SkillConfig, featuresDir: string, journeysDir: string, seededBreaksDir: string, seededDoorsDir: string }} Skill
|
|
14
|
+
* @typedef {{ playground: string, renderer?: string, prepare: string[][], userFacing: string[], notUserFacing: string[], smoke: string, smokeJourney: string, doors: Record<string, { globs: string[], funnels: string[], containments: string[], why: string } | null>, formal: import("./types.mjs").FormalCheck[] }} SkillConfig
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {string} text A feature file
|
|
19
|
+
* @returns {Map<string, string[]>} The lists of its frontmatter
|
|
20
|
+
*/
|
|
10
21
|
function frontmatterLists(text) {
|
|
22
|
+
/** @type {Map<string, string[]>} */
|
|
11
23
|
const lists = new Map();
|
|
12
24
|
const match = /^---\n([\s\S]*?)\n---\n/.exec(text);
|
|
25
|
+
/** @type {string[] | undefined} */
|
|
13
26
|
let current;
|
|
14
27
|
for (const line of (match?.[1] ?? "").split("\n")) {
|
|
15
28
|
const key = /^(\w+):\s*$/.exec(line);
|
|
@@ -24,6 +37,10 @@ function frontmatterLists(text) {
|
|
|
24
37
|
return lists;
|
|
25
38
|
}
|
|
26
39
|
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} cwd Where the command runs
|
|
42
|
+
* @returns {string} The one verification skill folder of the checkout
|
|
43
|
+
*/
|
|
27
44
|
function discover(cwd) {
|
|
28
45
|
const { root } = checkoutOf(cwd);
|
|
29
46
|
const skills = join(root ?? cwd, ".agents", "skills");
|
|
@@ -49,14 +66,18 @@ function discover(cwd) {
|
|
|
49
66
|
*
|
|
50
67
|
* @param {string | undefined} option The `--skill` folder
|
|
51
68
|
* @param {string} cwd Where the command runs
|
|
52
|
-
* @returns {Promise<
|
|
69
|
+
* @returns {Promise<import("./skill.mjs").Skill>} The skill
|
|
53
70
|
*/
|
|
54
71
|
export async function loadSkill(option, cwd) {
|
|
55
72
|
const dir = resolve(option ?? discover(cwd));
|
|
56
73
|
const { root } = checkoutOf(dir);
|
|
57
74
|
if (!root) throw new Error(`${dir} is not inside a git checkout`);
|
|
58
|
-
const
|
|
59
|
-
|
|
75
|
+
const file = join(dir, CONFIG_FILE);
|
|
76
|
+
/** @type {unknown} */
|
|
77
|
+
const loaded = await import(pathToFileURL(file).href);
|
|
78
|
+
const config = decodeSkillConfig(
|
|
79
|
+
isRecord(loaded) ? loaded.default : undefined,
|
|
80
|
+
file,
|
|
60
81
|
);
|
|
61
82
|
return {
|
|
62
83
|
dir,
|
|
@@ -74,7 +95,7 @@ export async function loadSkill(option, cwd) {
|
|
|
74
95
|
* Every feature file under `features/`, README.md excluded, with its
|
|
75
96
|
* `covers:` globs, `docs:` pages and text.
|
|
76
97
|
*
|
|
77
|
-
* @param {
|
|
98
|
+
* @param {Skill} skill The skill
|
|
78
99
|
* @returns {{ name: string, file: string, covers: string[], docs: string[], body: string }[]} The features
|
|
79
100
|
*/
|
|
80
101
|
export function readFeatures(skill) {
|
|
@@ -97,7 +118,7 @@ export function readFeatures(skill) {
|
|
|
97
118
|
/**
|
|
98
119
|
* The journeys module of a feature.
|
|
99
120
|
*
|
|
100
|
-
* @param {
|
|
121
|
+
* @param {Skill} skill The skill
|
|
101
122
|
* @param {string} feature The feature name
|
|
102
123
|
* @returns {string} Its path
|
|
103
124
|
*/
|
|
@@ -109,7 +130,7 @@ export function journeysFile(skill, feature) {
|
|
|
109
130
|
* Runs the config's `prepare` commands (install and build) in a checkout of
|
|
110
131
|
* the repository, so a run starts from a fresh build.
|
|
111
132
|
*
|
|
112
|
-
* @param {
|
|
133
|
+
* @param {Skill} skill The skill
|
|
113
134
|
* @param {string} cwd The checkout
|
|
114
135
|
* @throws When a command fails, with its output
|
|
115
136
|
*/
|