@bongos/core 1.21.176 → 1.21.177
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/.bongos-core.json +61 -361
- package/.claude/skills/planning-session/SKILL.md +2 -2
- package/config/scheduled-routines.json +3 -8
- package/docs/adr/0091-bounding-the-kernel-and-db-carve.md +1 -1
- package/docs/adr/0093-tranche-2-core-carve-sequence.md +1 -1
- package/docs/canonical-permissions.md +1 -1
- package/docs/file-map.md +3 -8
- package/docs/handoff-template.md +1 -1
- package/docs/module-api-changelog.md +2 -0
- package/docs/onboarding/diagrams/README.md +3 -7
- package/docs/onboarding/diagrams/assertions.json +3 -3
- package/docs/recipes/ops-gotchas.md +3 -1
- package/modules/autonomy/db.js +9 -0
- package/modules/autonomy/module.json +2 -2
- package/modules/autonomy/pollers/daily-checks.js +262 -0
- package/modules/autonomy/pollers/routine-timers.js +7 -5
- package/modules/autonomy/rank-roles.js +64 -0
- package/modules/autonomy/server-schedule.js +37 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +14 -0
- package/scripts/bongos/api-alias-caller-scan.js +2 -2
- package/scripts/bongos/audit-authorship.sh +0 -5
- package/scripts/bongos/audit-rank-roles.js +15 -56
- package/scripts/bongos/codemod-rename-scripts-gds.js +0 -1
- package/scripts/bongos/diagram-facts.js +1 -1
- package/scripts/bongos/do-api.js +1 -1
- package/scripts/bongos/fitness-checks-identity.js +4 -15
- package/scripts/bongos/gen-diagrams.js +3 -3
- package/scripts/bongos/pulse.js +5 -3
- package/scripts/bongos/routine-schedule.js +38 -3
- package/scripts/bongos/ship-regen.js +2 -3
- package/scripts/security/dep-audit.js +50 -124
- package/src/module-api.js +1 -1
- package/tests/autonomy_daily_checks.mjs +201 -0
- package/tests/claude_materialize.mjs +12 -12
- package/tests/effective_visibility_predicate.mjs +0 -18
- package/tests/routine_capture_kind.mjs +18 -7
- package/tests/routine_schedule.mjs +42 -0
- package/scripts/bongos/artifact-staleness.js +0 -267
- package/scripts/bongos/classify-discipline.js +0 -153
- package/scripts/bongos/codemod-rename-src-gds.js +0 -145
- package/scripts/bongos/coreB-carve-proof.sh +0 -725
- package/scripts/bongos/goal-names.js +0 -102
- package/scripts/bongos/rename-goals.js +0 -109
- package/scripts/bongos/seed-adopt-tasks.js +0 -75
- package/scripts/bongos/seed-api-contract-goal-tasks.js +0 -141
- package/scripts/bongos/seed-board-room-tasks.js +0 -196
- package/scripts/bongos/seed-bongos-app-tasks.js +0 -294
- package/scripts/bongos/seed-bongos-coreB-tranche1-tasks.js +0 -81
- package/scripts/bongos/seed-bongos-coreB-tranche2-tasks.js +0 -100
- package/scripts/bongos/seed-bongos-goal-hierarchy-tasks.js +0 -307
- package/scripts/bongos/seed-bongos-module-system-tasks.js +0 -346
- package/scripts/bongos/seed-bongos-tasks.js +0 -214
- package/scripts/bongos/seed-bongos-v1-seo-tasks.js +0 -104
- package/scripts/bongos/seed-bv1-community-tasks.js +0 -131
- package/scripts/bongos/seed-bv1-privacy-invites-tasks.js +0 -133
- package/scripts/bongos/seed-bv1-profile-page-tasks.js +0 -161
- package/scripts/bongos/seed-bv1-r01-delta-tasks.js +0 -154
- package/scripts/bongos/seed-bv2-tweak-mode-tasks.js +0 -277
- package/scripts/bongos/seed-gds-v4-tasks.js +0 -624
- package/scripts/bongos/seed-governance-tasks.js +0 -340
- package/scripts/bongos/seed-medusa-tasks.js +0 -440
- package/scripts/bongos/seed-module-upstreaming.js +0 -219
- package/scripts/bongos/seed-productization-tasks.js +0 -235
- package/scripts/bongos/seed-project-admin-console-tasks.js +0 -88
- package/scripts/bongos/seed-provisioning-tasks.js +0 -83
- package/scripts/bongos/seed-strict-versioning-tasks.js +0 -275
- package/scripts/bongos/seed-v2-world-tasks.js +0 -283
- package/scripts/gds/artifact-staleness.js +0 -7
- package/scripts/gds/classify-discipline.js +0 -7
- package/scripts/gds/codemod-rename-src-gds.js +0 -7
- package/scripts/gds/coreB-carve-proof.sh +0 -8
- package/scripts/gds/goal-names.js +0 -7
- package/scripts/gds/rename-goals.js +0 -7
- package/scripts/gds/seed-adopt-tasks.js +0 -7
- package/scripts/gds/seed-api-contract-goal-tasks.js +0 -7
- package/scripts/gds/seed-board-room-tasks.js +0 -6
- package/scripts/gds/seed-bongos-app-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche1-tasks.js +0 -7
- package/scripts/gds/seed-bongos-coreB-tranche2-tasks.js +0 -7
- package/scripts/gds/seed-bongos-goal-hierarchy-tasks.js +0 -7
- package/scripts/gds/seed-bongos-module-system-tasks.js +0 -7
- package/scripts/gds/seed-bongos-tasks.js +0 -7
- package/scripts/gds/seed-bongos-v1-seo-tasks.js +0 -6
- package/scripts/gds/seed-bv1-community-tasks.js +0 -6
- package/scripts/gds/seed-bv1-privacy-invites-tasks.js +0 -6
- package/scripts/gds/seed-bv1-profile-page-tasks.js +0 -6
- package/scripts/gds/seed-bv1-r01-delta-tasks.js +0 -6
- package/scripts/gds/seed-bv2-tweak-mode-tasks.js +0 -6
- package/scripts/gds/seed-gds-v4-tasks.js +0 -7
- package/scripts/gds/seed-governance-tasks.js +0 -7
- package/scripts/gds/seed-medusa-tasks.js +0 -7
- package/scripts/gds/seed-module-upstreaming.js +0 -7
- package/scripts/gds/seed-productization-tasks.js +0 -7
- package/scripts/gds/seed-project-admin-console-tasks.js +0 -6
- package/scripts/gds/seed-provisioning-tasks.js +0 -7
- package/scripts/gds/seed-strict-versioning-tasks.js +0 -7
- package/scripts/gds/seed-v2-world-tasks.js +0 -7
- package/scripts/migrate_cost_ledger.sh +0 -178
- package/tests/artifact_staleness.mjs +0 -207
- package/tests/goal_names.mjs +0 -120
- package/tests/newcomer_generator.mjs +0 -48
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-adopt-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-adopt-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-adopt-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-api-contract-goal-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-api-contract-goal-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-api-contract-goal-tasks.js');
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// scripts/gds/seed-board-room-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
-
// The real script is scripts/bongos/seed-board-room-tasks.js. This forwarder keeps callers that still name
|
|
4
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
-
'use strict';
|
|
6
|
-
require('../bongos/forwarder.js').forward(module, 'seed-board-room-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-bongos-app-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-bongos-app-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bongos-app-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-bongos-coreB-tranche1-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-bongos-coreB-tranche1-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bongos-coreB-tranche1-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-bongos-coreB-tranche2-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-bongos-coreB-tranche2-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bongos-coreB-tranche2-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-bongos-goal-hierarchy-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-bongos-goal-hierarchy-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bongos-goal-hierarchy-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-bongos-module-system-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-bongos-module-system-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bongos-module-system-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-bongos-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-bongos-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bongos-tasks.js');
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// scripts/gds/seed-bongos-v1-seo-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
-
// The real script is scripts/bongos/seed-bongos-v1-seo-tasks.js. This forwarder keeps callers that still name
|
|
4
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
-
'use strict';
|
|
6
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bongos-v1-seo-tasks.js');
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// scripts/gds/seed-bv1-community-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
-
// The real script is scripts/bongos/seed-bv1-community-tasks.js. This forwarder keeps callers that still name
|
|
4
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
-
'use strict';
|
|
6
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bv1-community-tasks.js');
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// scripts/gds/seed-bv1-privacy-invites-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
-
// The real script is scripts/bongos/seed-bv1-privacy-invites-tasks.js. This forwarder keeps callers that still name
|
|
4
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
-
'use strict';
|
|
6
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bv1-privacy-invites-tasks.js');
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// scripts/gds/seed-bv1-profile-page-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
-
// The real script is scripts/bongos/seed-bv1-profile-page-tasks.js. This forwarder keeps callers that still name
|
|
4
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
-
'use strict';
|
|
6
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bv1-profile-page-tasks.js');
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// scripts/gds/seed-bv1-r01-delta-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
-
// The real script is scripts/bongos/seed-bv1-r01-delta-tasks.js. This forwarder keeps callers that still name
|
|
4
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
-
'use strict';
|
|
6
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bv1-r01-delta-tasks.js');
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// scripts/gds/seed-bv2-tweak-mode-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
-
// The real script is scripts/bongos/seed-bv2-tweak-mode-tasks.js. This forwarder keeps callers that still name
|
|
4
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
-
'use strict';
|
|
6
|
-
require('../bongos/forwarder.js').forward(module, 'seed-bv2-tweak-mode-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-gds-v4-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-gds-v4-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-gds-v4-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-governance-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-governance-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-governance-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-medusa-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-medusa-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-medusa-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-module-upstreaming.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-module-upstreaming.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-module-upstreaming.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-productization-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-productization-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-productization-tasks.js');
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
// scripts/gds/seed-project-admin-console-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
2
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
3
|
-
// The real script is scripts/bongos/seed-project-admin-console-tasks.js. This forwarder keeps callers that still name
|
|
4
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
5
|
-
'use strict';
|
|
6
|
-
require('../bongos/forwarder.js').forward(module, 'seed-project-admin-console-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-provisioning-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-provisioning-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-provisioning-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-strict-versioning-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-strict-versioning-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-strict-versioning-tasks.js');
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// scripts/gds/seed-v2-world-tasks.js — @bongos-forwarder (task 1004848). Generated by
|
|
3
|
-
// scripts/bongos/codemod-rename-scripts-gds.js; do not edit.
|
|
4
|
-
// The real script is scripts/bongos/seed-v2-world-tasks.js. This forwarder keeps callers that still name
|
|
5
|
-
// the old path working, and warns once so they get repointed. Task 1003706 removes it.
|
|
6
|
-
'use strict';
|
|
7
|
-
require('../bongos/forwarder.js').forward(module, 'seed-v2-world-tasks.js');
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# scripts/migrate_cost_ledger.sh
|
|
3
|
-
#
|
|
4
|
-
# One-shot (idempotent) migration: fold any per-worktree
|
|
5
|
-
# art/iterations/cost_ledger.jsonl files into the shared location at
|
|
6
|
-
# ~/.config/otb/iterations/cost_ledger.jsonl, then rebuild cost_summary.json
|
|
7
|
-
# from the merged ledger.
|
|
8
|
-
#
|
|
9
|
-
# Per GDS #140 (2026-05-10 Lars decision): the ledger lives next to
|
|
10
|
-
# pms-session.json + GOOGLE_AI_STUDIO_KEY in $HOME so it survives worktree churn
|
|
11
|
-
# the way the API key does.
|
|
12
|
-
#
|
|
13
|
-
# Safe to re-run: appends only ledger rows not already present in the shared
|
|
14
|
-
# file (matched by (ts, model, kind, tile, attempt, cost_usd, notes) tuple).
|
|
15
|
-
# Per-worktree files are renamed to *.migrated-<timestamp> instead of deleted,
|
|
16
|
-
# so nothing is destroyed.
|
|
17
|
-
#
|
|
18
|
-
# Usage:
|
|
19
|
-
# scripts/migrate_cost_ledger.sh # search standard locations
|
|
20
|
-
# scripts/migrate_cost_ledger.sh <ledger> [...] # explicit files to fold in
|
|
21
|
-
# OTB_LEDGER_DIR=/tmp/foo scripts/migrate_cost_ledger.sh # override target
|
|
22
|
-
|
|
23
|
-
set -euo pipefail
|
|
24
|
-
|
|
25
|
-
SHARED_DIR="${OTB_LEDGER_DIR:-$HOME/.config/otb/iterations}"
|
|
26
|
-
SHARED_LEDGER="$SHARED_DIR/cost_ledger.jsonl"
|
|
27
|
-
SHARED_SUMMARY="$SHARED_DIR/cost_summary.json"
|
|
28
|
-
|
|
29
|
-
mkdir -p "$SHARED_DIR"
|
|
30
|
-
touch "$SHARED_LEDGER"
|
|
31
|
-
|
|
32
|
-
# Build candidate list: explicit args win, otherwise sweep common locations.
|
|
33
|
-
declare -a CANDIDATES=()
|
|
34
|
-
if [ "$#" -gt 0 ]; then
|
|
35
|
-
CANDIDATES=("$@")
|
|
36
|
-
else
|
|
37
|
-
# Repo root (this script lives in <root>/scripts/).
|
|
38
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
39
|
-
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
40
|
-
|
|
41
|
-
# 1. The current repo's per-worktree ledger.
|
|
42
|
-
[ -f "$REPO_ROOT/art/iterations/cost_ledger.jsonl" ] && \
|
|
43
|
-
CANDIDATES+=("$REPO_ROOT/art/iterations/cost_ledger.jsonl")
|
|
44
|
-
|
|
45
|
-
# 2. Any sibling worktrees of this repo.
|
|
46
|
-
if [ -d "$REPO_ROOT/.claude/worktrees" ]; then
|
|
47
|
-
while IFS= read -r p; do
|
|
48
|
-
CANDIDATES+=("$p")
|
|
49
|
-
done < <(find "$REPO_ROOT/.claude/worktrees" -mindepth 2 -maxdepth 4 \
|
|
50
|
-
-type f -name cost_ledger.jsonl 2>/dev/null)
|
|
51
|
-
fi
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
|
-
# De-dup the candidate list AND drop the shared-ledger path itself if it snuck in.
|
|
55
|
-
# (Stays portable to macOS default bash 3.2 — no associative arrays.)
|
|
56
|
-
SHARED_REAL="$(cd "$SHARED_DIR" && pwd)/cost_ledger.jsonl"
|
|
57
|
-
SEEN_LIST=""
|
|
58
|
-
FILTERED=()
|
|
59
|
-
for c in "${CANDIDATES[@]+"${CANDIDATES[@]}"}"; do
|
|
60
|
-
[ -f "$c" ] || continue
|
|
61
|
-
REAL="$(cd "$(dirname "$c")" && pwd)/$(basename "$c")"
|
|
62
|
-
[ "$REAL" = "$SHARED_REAL" ] && continue
|
|
63
|
-
case "$SEEN_LIST" in
|
|
64
|
-
*"|$REAL|"*) continue ;;
|
|
65
|
-
esac
|
|
66
|
-
SEEN_LIST="$SEEN_LIST|$REAL|"
|
|
67
|
-
FILTERED+=("$REAL")
|
|
68
|
-
done
|
|
69
|
-
|
|
70
|
-
if [ "${#FILTERED[@]}" -eq 0 ]; then
|
|
71
|
-
echo "No per-worktree cost_ledger.jsonl files to migrate."
|
|
72
|
-
echo "Shared ledger: $SHARED_LEDGER"
|
|
73
|
-
# Still rebuild the summary so a fresh shared dir gets a summary file.
|
|
74
|
-
python3 "$(dirname "$0")/../modules/art-pipeline/pipeline/cost_ledger.py" status >/dev/null || true
|
|
75
|
-
exit 0
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
echo "Migrating to: $SHARED_LEDGER"
|
|
79
|
-
for f in "${FILTERED[@]}"; do echo " source: $f"; done
|
|
80
|
-
|
|
81
|
-
# Python does the dedup + merge + summary rebuild. Keeps the bash side simple
|
|
82
|
-
# and gives us proper JSON-aware comparison.
|
|
83
|
-
python3 - "$SHARED_LEDGER" "$SHARED_SUMMARY" "${FILTERED[@]}" <<'PYEOF'
|
|
84
|
-
import json, os, sys, time
|
|
85
|
-
from pathlib import Path
|
|
86
|
-
|
|
87
|
-
shared_ledger = Path(sys.argv[1])
|
|
88
|
-
shared_summary = Path(sys.argv[2])
|
|
89
|
-
sources = [Path(p) for p in sys.argv[3:]]
|
|
90
|
-
|
|
91
|
-
def row_key(r: dict) -> tuple:
|
|
92
|
-
# Identity: (ts, model, kind, tile, attempt, cost_usd, notes).
|
|
93
|
-
# No `id` column exists today, so dedup by content tuple.
|
|
94
|
-
return (
|
|
95
|
-
r.get("ts", ""),
|
|
96
|
-
r.get("model", ""),
|
|
97
|
-
r.get("kind", ""),
|
|
98
|
-
r.get("tile", ""),
|
|
99
|
-
r.get("attempt", 0),
|
|
100
|
-
round(float(r.get("cost_usd", 0.0)), 6),
|
|
101
|
-
r.get("notes", ""),
|
|
102
|
-
)
|
|
103
|
-
|
|
104
|
-
existing_keys: set = set()
|
|
105
|
-
if shared_ledger.exists():
|
|
106
|
-
with shared_ledger.open() as f:
|
|
107
|
-
for line in f:
|
|
108
|
-
line = line.strip()
|
|
109
|
-
if not line:
|
|
110
|
-
continue
|
|
111
|
-
try:
|
|
112
|
-
existing_keys.add(row_key(json.loads(line)))
|
|
113
|
-
except Exception:
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
added = 0
|
|
117
|
-
skipped_dup = 0
|
|
118
|
-
skipped_bad = 0
|
|
119
|
-
ts_suffix = time.strftime("%Y%m%dT%H%M%S")
|
|
120
|
-
|
|
121
|
-
with shared_ledger.open("a") as out:
|
|
122
|
-
for src in sources:
|
|
123
|
-
if not src.exists():
|
|
124
|
-
continue
|
|
125
|
-
with src.open() as f:
|
|
126
|
-
for line in f:
|
|
127
|
-
line_stripped = line.strip()
|
|
128
|
-
if not line_stripped:
|
|
129
|
-
continue
|
|
130
|
-
try:
|
|
131
|
-
row = json.loads(line_stripped)
|
|
132
|
-
except Exception:
|
|
133
|
-
skipped_bad += 1
|
|
134
|
-
continue
|
|
135
|
-
k = row_key(row)
|
|
136
|
-
if k in existing_keys:
|
|
137
|
-
skipped_dup += 1
|
|
138
|
-
continue
|
|
139
|
-
existing_keys.add(k)
|
|
140
|
-
out.write(line if line.endswith("\n") else line_stripped + "\n")
|
|
141
|
-
added += 1
|
|
142
|
-
# Rename source so a re-run doesn't re-process it AND nothing is destroyed.
|
|
143
|
-
try:
|
|
144
|
-
src.rename(src.with_suffix(src.suffix + f".migrated-{ts_suffix}"))
|
|
145
|
-
print(f" archived: {src} → {src.name}.migrated-{ts_suffix}")
|
|
146
|
-
except OSError as e:
|
|
147
|
-
print(f" warn: could not rename {src}: {e}", file=sys.stderr)
|
|
148
|
-
|
|
149
|
-
# Rebuild summary from the merged ledger.
|
|
150
|
-
total = 0.0
|
|
151
|
-
n = 0
|
|
152
|
-
with shared_ledger.open() as f:
|
|
153
|
-
for line in f:
|
|
154
|
-
line = line.strip()
|
|
155
|
-
if not line:
|
|
156
|
-
continue
|
|
157
|
-
try:
|
|
158
|
-
total += float(json.loads(line).get("cost_usd", 0.0))
|
|
159
|
-
n += 1
|
|
160
|
-
except Exception:
|
|
161
|
-
pass
|
|
162
|
-
|
|
163
|
-
HARD_STOP = 80.00
|
|
164
|
-
shared_summary.parent.mkdir(parents=True, exist_ok=True)
|
|
165
|
-
shared_summary.write_text(json.dumps({
|
|
166
|
-
"total_usd": round(total, 4),
|
|
167
|
-
"calls": n,
|
|
168
|
-
"hard_stop_usd": HARD_STOP,
|
|
169
|
-
"remaining_usd": round(HARD_STOP - total, 4),
|
|
170
|
-
"updated_at": time.strftime("%Y-%m-%dT%H:%M:%S"),
|
|
171
|
-
}, indent=2))
|
|
172
|
-
|
|
173
|
-
print(f"\nMerged: {added} new rows, {skipped_dup} duplicates skipped, {skipped_bad} malformed lines skipped.")
|
|
174
|
-
print(f"Shared ledger now: {n} rows, ${total:.4f} spent of ${HARD_STOP:.2f} cap.")
|
|
175
|
-
PYEOF
|
|
176
|
-
|
|
177
|
-
echo
|
|
178
|
-
echo "Done. Verify with: python3 modules/art-pipeline/pipeline/cost_ledger.py status"
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
// tests/artifact_staleness.mjs
|
|
2
|
-
//
|
|
3
|
-
// Covers scripts/bongos/artifact-staleness.js (task 1001930, ADR 0110 pt.5) — the backstop
|
|
4
|
-
// for the gitignored, deploy-generated navigation docs.
|
|
5
|
-
//
|
|
6
|
-
// ADR 0110 untracked docs/repo-map.md + docs/session-log-index.md to kill a merge-collision
|
|
7
|
-
// class, and accepted a debt in writing: a deploy generator that breaks leaves a stale or
|
|
8
|
-
// absent file with no blocking gate, alarming only via a ::ERROR:: line in journald. This
|
|
9
|
-
// check is that backstop, so the properties worth pinning are the ones that decide whether
|
|
10
|
-
// anyone will still be reading it in six months:
|
|
11
|
-
//
|
|
12
|
-
// 1. it distinguishes CANNOT-CHECK from FOUND-SOMETHING (a monitor that cries wolf when
|
|
13
|
-
// it is merely in the wrong place is one nobody reads — the exact ADR 0110 failure);
|
|
14
|
-
// 2. absent and stale stay separate (they send a reader to two different places);
|
|
15
|
-
// 3. the registry cannot silently fall behind the set of gitignored artifacts.
|
|
16
|
-
//
|
|
17
|
-
// `run`/`exists` are injected throughout, so nothing here touches a real generator, git
|
|
18
|
-
// repo, or network. The end-to-end behaviour against the REAL generators was verified by
|
|
19
|
-
// hand at ship time (fresh / absent / stale / both), which fakes cannot prove.
|
|
20
|
-
//
|
|
21
|
-
// Run: node --test tests/artifact_staleness.mjs
|
|
22
|
-
|
|
23
|
-
import { test } from 'node:test';
|
|
24
|
-
import path from 'node:path';
|
|
25
|
-
import assert from 'node:assert/strict';
|
|
26
|
-
import { createRequire } from 'node:module';
|
|
27
|
-
import { readFileSync, readdirSync } from 'node:fs';
|
|
28
|
-
|
|
29
|
-
const require = createRequire(import.meta.url);
|
|
30
|
-
const { ARTIFACTS, atMain, checkArtifact, check, report } = require('../scripts/bongos/artifact-staleness.js');
|
|
31
|
-
|
|
32
|
-
const SHA = 'a'.repeat(40);
|
|
33
|
-
const OTHER = 'b'.repeat(40);
|
|
34
|
-
|
|
35
|
-
// A fake `run` for git: a table keyed by the git subcommand+args it is asked.
|
|
36
|
-
function fakeGit({ head = SHA, main = SHA, porcelain = '', branch = 'main', fail = null } = {}) {
|
|
37
|
-
return (cmd, args) => {
|
|
38
|
-
const key = args.join(' ');
|
|
39
|
-
if (fail && key.includes(fail)) return { status: 1, stdout: '', stderr: 'boom' };
|
|
40
|
-
if (key === 'rev-parse HEAD') return { status: 0, stdout: `${head}\n` };
|
|
41
|
-
if (key === 'rev-parse origin/main') return { status: 0, stdout: `${main}\n` };
|
|
42
|
-
if (key === 'rev-parse --abbrev-ref HEAD') return { status: 0, stdout: `${branch}\n` };
|
|
43
|
-
if (key === 'status --porcelain') return { status: 0, stdout: porcelain };
|
|
44
|
-
return { status: 0, stdout: '' };
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// ── the guard: when is a comparison even meaningful? ────────────────────────────
|
|
49
|
-
|
|
50
|
-
test('at origin/main with a clean tree is comparable', () => {
|
|
51
|
-
assert.equal(atMain(fakeGit()).ok, true);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test('a feature branch is INCONCLUSIVE — the artifact would be judged against the wrong code', () => {
|
|
55
|
-
const r = atMain(fakeGit({ head: OTHER, branch: 'claude/some-task' }));
|
|
56
|
-
assert.equal(r.ok, false);
|
|
57
|
-
assert.match(r.reason, /claude\/some-task/);
|
|
58
|
-
assert.match(r.reason, /not origin\/main/);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test('a DIRTY tree is inconclusive — a generator regenerates from the working tree, not from main', () => {
|
|
62
|
-
// The real bug this caught: running the check against its own uncommitted source file
|
|
63
|
-
// reported repo-map.md stale when it in fact matched main.
|
|
64
|
-
const r = atMain(fakeGit({ porcelain: ' M scripts/bongos/artifact-staleness.js\n' }));
|
|
65
|
-
assert.equal(r.ok, false);
|
|
66
|
-
assert.match(r.reason, /not clean/);
|
|
67
|
-
assert.match(r.reason, /scripts\/(?:gds|bongos)\/artifact-staleness\.js/);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
test('every porcelain status column is parsed whole — an unstaged " M" must not lose its first letter', () => {
|
|
71
|
-
// The SECOND bug this file caught: the reader trimmed the buffer, and an unstaged
|
|
72
|
-
// modification's leading space is significant, so the first line printed "cripts/…".
|
|
73
|
-
// One case per column shape, first-line position included, since only the first line
|
|
74
|
-
// was ever affected.
|
|
75
|
-
for (const line of [' M scripts/bongos/artifact-staleness.js', 'M scripts/bongos/artifact-staleness.js', '?? scripts/bongos/artifact-staleness.js', 'A scripts/bongos/artifact-staleness.js']) {
|
|
76
|
-
const r = atMain(fakeGit({ porcelain: `${line}\n` }));
|
|
77
|
-
assert.match(r.reason, /\(scripts\/(?:gds|bongos)\/artifact-staleness\.js\)/, `mangled the path for "${line}"`);
|
|
78
|
-
}
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
test('a long dirty list is truncated with a count rather than dumped', () => {
|
|
82
|
-
const porcelain = [' M a.js', ' M b.js', ' M c.js', ' M d.js', ' M e.js'].join('\n') + '\n';
|
|
83
|
-
const r = atMain(fakeGit({ porcelain }));
|
|
84
|
-
assert.match(r.reason, /a\.js, b\.js, c\.js, \+2 more/);
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test('git being unable to answer is inconclusive, never a pass', () => {
|
|
88
|
-
assert.equal(atMain(fakeGit({ fail: 'rev-parse origin/main' })).ok, false);
|
|
89
|
-
assert.equal(atMain(fakeGit({ fail: 'status --porcelain' })).ok, false);
|
|
90
|
-
assert.equal(atMain(fakeGit({ fail: 'rev-parse HEAD' })).ok, false);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
test('check() REFUSES rather than reporting a finding when the checkout is not comparable', () => {
|
|
94
|
-
assert.throws(
|
|
95
|
-
() => check({ run: fakeGit({ head: OTHER }), exists: () => true }),
|
|
96
|
-
(e) => e.inconclusive === true,
|
|
97
|
-
'an inconclusive run must be distinguishable from a finding',
|
|
98
|
-
);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
// ── one artifact's verdict ──────────────────────────────────────────────────────
|
|
102
|
-
|
|
103
|
-
const A = { rel: 'docs/repo-map.md', generator: 'gen-repo-map.js', what: 'the symbol skeleton' };
|
|
104
|
-
const gen = (status, extra = {}) => () => ({ status, stdout: '', stderr: '', ...extra });
|
|
105
|
-
|
|
106
|
-
test('generator exit 0 → fresh', () => {
|
|
107
|
-
assert.equal(checkArtifact(A, { run: gen(0), exists: () => true }).state, 'fresh');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test('generator exit 2 + file present → stale; + file absent → absent', () => {
|
|
111
|
-
assert.equal(checkArtifact(A, { run: gen(2), exists: () => true }).state, 'stale');
|
|
112
|
-
assert.equal(checkArtifact(A, { run: gen(2), exists: () => false }).state, 'absent');
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
test('a generator that cannot answer is UNKNOWN, never stale — it is not a finding about the artifact', () => {
|
|
116
|
-
const crashed = checkArtifact(A, { run: gen(1, { stderr: 'SyntaxError: bad\n' }), exists: () => true });
|
|
117
|
-
assert.equal(crashed.state, 'unknown');
|
|
118
|
-
assert.match(crashed.detail, /SyntaxError/);
|
|
119
|
-
const spawnFailed = checkArtifact(A, { run: () => ({ error: new Error('ENOENT') }), exists: () => true });
|
|
120
|
-
assert.equal(spawnFailed.state, 'unknown');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// ── the report a human actually reads ───────────────────────────────────────────
|
|
124
|
-
|
|
125
|
-
const runFor = (byGenerator) => (cmd, args) => {
|
|
126
|
-
if (cmd === 'git') return fakeGit()(cmd, args);
|
|
127
|
-
// path.basename, not split('/') — the checker passes an ABSOLUTE generator path,
|
|
128
|
-
// which is backslash-separated on Windows, so this returned the whole path, matched
|
|
129
|
-
// no key in byGenerator, and every generator reported `status: undefined` -> "exit
|
|
130
|
-
// undefined". Three suites carried this same idiom (task 1003848).
|
|
131
|
-
const which = path.basename(String(args[0] || ''));
|
|
132
|
-
return { status: byGenerator[which], stdout: '', stderr: '' };
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
test('all fresh → one quiet line, and no alarm language', () => {
|
|
136
|
-
const r = check({ run: runFor({ 'gen-repo-map.js': 0, 'gen-session-index.js': 0 }), exists: () => true });
|
|
137
|
-
assert.equal(r.stale.length + r.absent.length, 0);
|
|
138
|
-
const text = report(r);
|
|
139
|
-
assert.match(text, /current/);
|
|
140
|
-
assert.ok(!text.includes('⚠'), 'a healthy run must not look like a finding');
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('absent and stale are reported as different things, not one blurred count', () => {
|
|
144
|
-
const r = check({
|
|
145
|
-
run: runFor({ 'gen-repo-map.js': 2, 'gen-session-index.js': 2 }),
|
|
146
|
-
exists: (p) => !String(p).endsWith('repo-map.md'), // repo-map gone, session-index present
|
|
147
|
-
});
|
|
148
|
-
assert.deepEqual(r.absent.map((a) => a.rel), ['docs/repo-map.md']);
|
|
149
|
-
assert.deepEqual(r.stale.map((a) => a.rel), ['docs/session-log-index.md']);
|
|
150
|
-
const text = report(r);
|
|
151
|
-
assert.match(text, /docs\/repo-map\.md — MISSING entirely/);
|
|
152
|
-
assert.match(text, /docs\/session-log-index\.md — out of date/);
|
|
153
|
-
assert.match(text, /deploy step/, 'the report must say where to go look');
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test('an unchecked artifact is surfaced even when nothing is stale — unwatched must not read as healthy', () => {
|
|
157
|
-
const r = check({ run: runFor({ 'gen-repo-map.js': 0, 'gen-session-index.js': 1 }), exists: () => true });
|
|
158
|
-
assert.equal(r.stale.length + r.absent.length, 0);
|
|
159
|
-
assert.equal(r.unknown.length, 1);
|
|
160
|
-
assert.match(report(r), /Could not check 1 artifact/);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// ── the registry cannot fall behind reality ─────────────────────────────────────
|
|
164
|
-
|
|
165
|
-
test('every registered artifact is really gitignored — a tracked file belongs to the PR gate instead', () => {
|
|
166
|
-
const ignore = readFileSync(new URL('../.gitignore', import.meta.url), 'utf8')
|
|
167
|
-
.split('\n').map((l) => l.trim());
|
|
168
|
-
for (const a of ARTIFACTS) {
|
|
169
|
-
assert.ok(ignore.includes(a.rel), `${a.rel} must be gitignored to belong in this check`);
|
|
170
|
-
}
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test('every registered generator really exposes --check-whole — the contract this check is built on', () => {
|
|
174
|
-
for (const a of ARTIFACTS) {
|
|
175
|
-
const src = readFileSync(new URL(`../scripts/bongos/${a.generator}`, import.meta.url), 'utf8');
|
|
176
|
-
assert.match(src, /--check-whole/, `${a.generator} must support --check-whole`);
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
test('the two ADR 0110 artifacts are both covered, and the known exclusions stay deliberate', () => {
|
|
181
|
-
assert.deepEqual(ARTIFACTS.map((a) => a.rel).sort(), ['docs/repo-map.md', 'docs/session-log-index.md']);
|
|
182
|
-
// atlas.json and the diagram renders are gitignored + deploy-generated too, but neither
|
|
183
|
-
// can be answered by this mechanism (a timestamped payload; a render needing Chromium).
|
|
184
|
-
// The reasons are written out at ARTIFACTS — this asserts they were a decision, so that
|
|
185
|
-
// adding either later is a deliberate act rather than a silent widening.
|
|
186
|
-
const src = readFileSync(new URL('../scripts/bongos/artifact-staleness.js', import.meta.url), 'utf8');
|
|
187
|
-
assert.match(src, /atlas\.json/, 'the atlas exclusion must stay documented');
|
|
188
|
-
assert.match(src, /diagrams/, 'the diagram-render exclusion must stay documented');
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
test('this check must NOT be wired into CI — a fresh clone legitimately has none of these files', () => {
|
|
192
|
-
// The artifacts are gitignored, so a CI checkout never has them and this would be red on
|
|
193
|
-
// every single run — the "monitor nobody reads" failure ADR 0110 already has once. The
|
|
194
|
-
// mistake is an easy one to make by analogy with publish-watch.js, which IS a workflow
|
|
195
|
-
// (it reads git + the registry, so it is true anywhere). Assert no workflow invokes it.
|
|
196
|
-
const src = readFileSync(new URL('../scripts/bongos/artifact-staleness.js', import.meta.url), 'utf8');
|
|
197
|
-
assert.match(src, /NEVER in CI/, 'the file must state where it may run');
|
|
198
|
-
|
|
199
|
-
let names = [];
|
|
200
|
-
try { names = readdirSync(new URL('../.github/workflows', import.meta.url)); } catch { names = []; }
|
|
201
|
-
const callers = names.filter((n) => {
|
|
202
|
-
try {
|
|
203
|
-
return readFileSync(new URL(`../.github/workflows/${n}`, import.meta.url), 'utf8').includes('artifact-staleness');
|
|
204
|
-
} catch { return false; }
|
|
205
|
-
});
|
|
206
|
-
assert.deepEqual(callers, [], `no workflow may run this check; found: ${callers.join(', ')}`);
|
|
207
|
-
});
|