devlyn-cli 4.3.0 → 4.3.4
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/bin/cleanup-history.json +76 -1
- package/bin/devlyn.js +172 -48
- package/bin/skill-history.json +133 -2
- package/config/skills/_shared/harness-store.py +8 -2
- package/config/skills/_shared/platform-support.py +12 -0
- package/config/skills/_shared/task-completion.md +6 -3
- package/config/skills/_shared/warm-build-dirs.py +97 -2
- package/config/skills/devlyn-ideate/scripts/queue.py +10 -1
- package/harness/AGENTS.md +37 -0
- package/harness/CLAUDE.md +37 -0
- package/native/claude/.claude-plugin/plugin.json +6 -0
- package/native/claude/skills/_shared/.ruff.toml +2 -0
- package/native/claude/skills/_shared/adapters/claude.md +15 -0
- package/native/claude/skills/_shared/adapters/codex.md +14 -0
- package/native/claude/skills/_shared/adapters/omp.md +6 -0
- package/native/claude/skills/_shared/codex-monitored.sh +263 -0
- package/native/claude/skills/_shared/engine-doctor.sh +70 -0
- package/native/claude/skills/_shared/engine-preflight.md +29 -0
- package/native/claude/skills/_shared/expected-contract.py +242 -0
- package/native/claude/skills/_shared/expected.schema.json +120 -0
- package/native/claude/skills/_shared/harness-store.py +207 -0
- package/native/claude/skills/_shared/invocation-receipt.py +499 -0
- package/native/claude/skills/_shared/platform-support.py +407 -0
- package/native/claude/skills/_shared/role-config.py +247 -0
- package/native/claude/skills/_shared/runtime-principles.md +35 -0
- package/native/claude/skills/_shared/task-complete.py +2865 -0
- package/native/claude/skills/_shared/task-completion.md +249 -0
- package/native/claude/skills/_shared/warm-build-dirs.py +1065 -0
- package/native/claude/skills/devlyn-engines/SKILL.md +63 -0
- package/native/claude/skills/devlyn-ideate/SKILL.md +132 -0
- package/native/claude/skills/devlyn-ideate/references/elicitation.md +29 -0
- package/native/claude/skills/devlyn-ideate/references/loop.md +123 -0
- package/native/claude/skills/devlyn-ideate/references/package-format.md +128 -0
- package/native/claude/skills/devlyn-ideate/references/plan-add.md +12 -0
- package/native/claude/skills/devlyn-ideate/scripts/acceptance.py +882 -0
- package/native/claude/skills/devlyn-ideate/scripts/queue.py +2191 -0
- package/native/codex/.agents/plugins/marketplace.json +12 -0
- package/native/codex/plugins/devlyn/.codex-plugin/plugin.json +6 -0
- package/native/codex/plugins/devlyn/skills/_shared/.ruff.toml +2 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/claude.md +15 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/codex.md +14 -0
- package/native/codex/plugins/devlyn/skills/_shared/adapters/omp.md +6 -0
- package/native/codex/plugins/devlyn/skills/_shared/codex-monitored.sh +263 -0
- package/native/codex/plugins/devlyn/skills/_shared/engine-doctor.sh +70 -0
- package/native/codex/plugins/devlyn/skills/_shared/engine-preflight.md +29 -0
- package/native/codex/plugins/devlyn/skills/_shared/expected-contract.py +242 -0
- package/native/codex/plugins/devlyn/skills/_shared/expected.schema.json +120 -0
- package/native/codex/plugins/devlyn/skills/_shared/harness-store.py +207 -0
- package/native/codex/plugins/devlyn/skills/_shared/invocation-receipt.py +499 -0
- package/native/codex/plugins/devlyn/skills/_shared/platform-support.py +407 -0
- package/native/codex/plugins/devlyn/skills/_shared/role-config.py +247 -0
- package/native/codex/plugins/devlyn/skills/_shared/runtime-principles.md +35 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-complete.py +2865 -0
- package/native/codex/plugins/devlyn/skills/_shared/task-completion.md +249 -0
- package/native/codex/plugins/devlyn/skills/_shared/warm-build-dirs.py +1065 -0
- package/native/codex/plugins/devlyn/skills/devlyn-engines/SKILL.md +63 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/SKILL.md +132 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/elicitation.md +29 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/loop.md +123 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/package-format.md +128 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/references/plan-add.md +12 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/acceptance.py +882 -0
- package/native/codex/plugins/devlyn/skills/devlyn-ideate/scripts/queue.py +2191 -0
- package/package.json +1 -1
- package/release.json +1630 -0
package/bin/cleanup-history.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"source": "scripts/update-cleanup-history.js: LF-normalized Git blobs from first-parent config/agents-config/optional-skills history and
|
|
2
|
+
"source": "scripts/update-cleanup-history.js: LF-normalized Git blobs from first-parent config/agents-config/optional-skills history and release tags; excludes optional-addon hashes and third-party web-design-guidelines; commands: devlyn.*.md from config/commands, .claude/commands, commands and optional-commands/* in the same commits via skill-ownership.contentHash",
|
|
3
3
|
"files": {
|
|
4
4
|
".claude/agents/evaluator.md": [
|
|
5
5
|
"146a229b0218938bca2de6b426cda642ae9826dc2dfa8c3687c6fd5de9160cf6",
|
|
@@ -41,6 +41,81 @@
|
|
|
41
41
|
"d5615e73af2401ea7418e02fc76d850506e59a64fff00f084ad272e474e138ba"
|
|
42
42
|
]
|
|
43
43
|
},
|
|
44
|
+
"commands": {
|
|
45
|
+
"devlyn.clean.md": [
|
|
46
|
+
"918c6ae867c57a4d9748237adae0df8c78a1447c7c028a0af988b59a5fbb0d9a"
|
|
47
|
+
],
|
|
48
|
+
"devlyn.design-system.md": [
|
|
49
|
+
"f81166b5ba08d9ee1f5cff33c911ea387e5cb50141a9712a96fc4434c60a673f"
|
|
50
|
+
],
|
|
51
|
+
"devlyn.design-ui.md": [
|
|
52
|
+
"0496a6b2dd8c6bb0474b71ef9a319a6b95bb76972d0f07a01c33f8860fe83dfb",
|
|
53
|
+
"4e2aafcc0de6ce7f3022a353aaa9fb4330e76a50a91ab970b9b9a0f192f9f1f2"
|
|
54
|
+
],
|
|
55
|
+
"devlyn.discover-product.md": [
|
|
56
|
+
"1e66aa35bf300ec6167e1b888f43a92ebd9e60d501397ff0334c9a7809d433f5",
|
|
57
|
+
"a609401d29cd2bdad7a48c7c8f096b442fe03a17c6b2c3151936c47b4a16e6c6"
|
|
58
|
+
],
|
|
59
|
+
"devlyn.evaluate.md": [
|
|
60
|
+
"8f17ccc639d54adab24c49dcf471376bab97511675d00554056a4c1f67a14115"
|
|
61
|
+
],
|
|
62
|
+
"devlyn.feature-spec.md": [
|
|
63
|
+
"d442ed7ae8d14a255c37f1b120b758f69794be88e2970a8a0c0e51519344b6c7"
|
|
64
|
+
],
|
|
65
|
+
"devlyn.handoff.md": [
|
|
66
|
+
"91d143c6305e95e4af6992eda0d127e94a394c2e91a2d409b4d41ca03c23f470"
|
|
67
|
+
],
|
|
68
|
+
"devlyn.implement-ui.md": [
|
|
69
|
+
"a8d0813781b2f7a3df5a4751c92755bf7c84c53428cf63cea34c965939b8c478"
|
|
70
|
+
],
|
|
71
|
+
"devlyn.pencil-pull.md": [
|
|
72
|
+
"dfdd3d19ca676558bfa2bb3ce398181b0a081ab88d1feaf69c54c706a9671631"
|
|
73
|
+
],
|
|
74
|
+
"devlyn.pencil-push.md": [
|
|
75
|
+
"5f2e8b29609cbb2af72941579823bec598265ebf29cd3a375d6aa47d95b74354"
|
|
76
|
+
],
|
|
77
|
+
"devlyn.product-spec.md": [
|
|
78
|
+
"7a92d2df2586b44e2c4d9182eeb3d61f7adc2a13bed4d2cd26abcfe086585895"
|
|
79
|
+
],
|
|
80
|
+
"devlyn.recommend-features.md": [
|
|
81
|
+
"2b92523017e1da10c299dafb19d84736de5084b5b46693de7fd2d6df51dc56b4"
|
|
82
|
+
],
|
|
83
|
+
"devlyn.resolve.md": [
|
|
84
|
+
"24595766e1a81a1fcacb78261b03c3665bc686bcac8b1805d1c4db379584879f",
|
|
85
|
+
"48c0c3ee553f87f1f0733190effc7220c01c82837a5300ef68c9ffdf30c5d3a1",
|
|
86
|
+
"eaad5354cfc20ed378ffd2a2e1ca6bade1310bd2a47ab93e5942e9599c3ba9a8"
|
|
87
|
+
],
|
|
88
|
+
"devlyn.review.md": [
|
|
89
|
+
"22f532f587f6d2fbf2662e38d5073b85a030141573f86d92ad40b9d292836fb7",
|
|
90
|
+
"3a511fc678e599ff4b4c37fd9191399a599e1ba9ea9c40fc4afcbe29cb188d2b",
|
|
91
|
+
"55888e08bf31ead1d359f218277cfa7efc3c6cb0f96fadc665d5a2406ff115d3",
|
|
92
|
+
"7f9ce1db4ffd3cdaf9fd949e4b07de04e0931c91cd846d4ab220ff6114a45b17"
|
|
93
|
+
],
|
|
94
|
+
"devlyn.team-design-ui.md": [
|
|
95
|
+
"02485911588e9f52afb5a5082fd7804ee7a41bad44323960a2afe1a16a49d628"
|
|
96
|
+
],
|
|
97
|
+
"devlyn.team-resolve.md": [
|
|
98
|
+
"09fc163d34dfe8906dd8d22501a028bfbbad639499acab01e5e60f06fc520382",
|
|
99
|
+
"54c52c3f27998998ed55cc648d071bbb10444761b819c59ed118b795596a4aee",
|
|
100
|
+
"72f66be3c4c27a46e76d56d13a0d381880c054a03243188f98da2faf6dc77295",
|
|
101
|
+
"8f11fa0c3203c0dd5bf09b5243dcff5968a36275682c54b3a012262fc545859a",
|
|
102
|
+
"ccfc5083813ee50d407109616ccbc2509aa7526316cebbf7a271537abdfff3b7"
|
|
103
|
+
],
|
|
104
|
+
"devlyn.team-review.md": [
|
|
105
|
+
"58b36cfe2749968d697d486a104a1b139e4312730b0ac2933550634f6ece48db",
|
|
106
|
+
"7e5c2d93a23d51af7939dae29c70095dbd8a4a681cc06d4d578f2de29df346a3",
|
|
107
|
+
"c8e145955a303141af4082dae174e3b3f1681e4c9698e25ae337eadd31fb883b"
|
|
108
|
+
],
|
|
109
|
+
"devlyn.team-ui.md": [
|
|
110
|
+
"d0b10b922b0936d8826a1bbcf62c0f9ea4cc3ed79e6d7c6af0116e134de73aad"
|
|
111
|
+
],
|
|
112
|
+
"devlyn.ui.md": [
|
|
113
|
+
"3cb63dcbe3c455c97eca8b30057aefba8959936fce79c3d854adeb3c07ec8959"
|
|
114
|
+
],
|
|
115
|
+
"devlyn.update-docs.md": [
|
|
116
|
+
"86945afccf24762a88d26eef3703fbf6c5a90a3343fd3de72a9cb26b6f62d3b5"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
44
119
|
"skills": {
|
|
45
120
|
"code-health-standards": [
|
|
46
121
|
"962a82b5ebeafac515f81f5c68494c67dbf31c3e3e453f23c4452d4725e21355",
|
package/bin/devlyn.js
CHANGED
|
@@ -1347,7 +1347,7 @@ function showHelp() {
|
|
|
1347
1347
|
// is read-only and lists each item with its proof. Apply plans again from what is on disk now and
|
|
1348
1348
|
// acts only on items both plans name the same way, so a plan file can narrow a cleanup but never
|
|
1349
1349
|
// widen it: untracked copies go in place, tracked ones through the project's delivery policy.
|
|
1350
|
-
//
|
|
1350
|
+
// Every removal needs shipped-content proof; names only identify candidates.
|
|
1351
1351
|
// ---------------------------------------------------------------------------
|
|
1352
1352
|
|
|
1353
1353
|
const CLEANUP_CORE = ['devlyn-ideate', 'devlyn-engines'];
|
|
@@ -1394,6 +1394,26 @@ function cleanupInUse(dir) {
|
|
|
1394
1394
|
return cleanupProcesses === null || cleanupProcesses.some((line) => line.includes(dir + path.sep));
|
|
1395
1395
|
}
|
|
1396
1396
|
|
|
1397
|
+
// Markers are generated, not bundled: require the exact writer format, a shipped version and
|
|
1398
|
+
// only fingerprints of shipped skills. Extra fields, arbitrary hashes and non-files stay untouched.
|
|
1399
|
+
function cleanupMarkerOwned(file, current) {
|
|
1400
|
+
if (!fs.lstatSync(file)?.isFile()) return false;
|
|
1401
|
+
const text = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
|
1402
|
+
let marker;
|
|
1403
|
+
try { marker = JSON.parse(text); } catch (error) { if (error instanceof SyntaxError) return false; throw error; }
|
|
1404
|
+
if (!marker || (marker.version !== PKG.version && !SKILL_HISTORY.versions.includes(marker.version))) return false;
|
|
1405
|
+
// 2.10.1 to 4.2.0 wrote no skills field.
|
|
1406
|
+
const [major, minor, patch] = marker.version.split('.').map(Number);
|
|
1407
|
+
if (major < 4 || (major === 4 && (minor < 2 || (minor === 2 && patch === 0)))) {
|
|
1408
|
+
return text === JSON.stringify({ schemaVersion: 1, package: PKG.name, version: marker.version }, null, 2) + '\n';
|
|
1409
|
+
}
|
|
1410
|
+
if (!marker.skills || typeof marker.skills !== 'object' || Array.isArray(marker.skills)) return false;
|
|
1411
|
+
const entries = Object.entries(marker.skills);
|
|
1412
|
+
if (!entries.length || entries.some(([name, hash]) => current[name] !== hash
|
|
1413
|
+
&& !(Object.hasOwn(SKILL_HISTORY.skills, name) && SKILL_HISTORY.skills[name].includes(hash)))) return false;
|
|
1414
|
+
return text === JSON.stringify({ schemaVersion: 1, package: PKG.name, version: marker.version, skills: marker.skills }, null, 2) + '\n';
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1397
1417
|
// The legacy items of one skill root, each with its proof and the base its parents are checked from.
|
|
1398
1418
|
// `_shared` and the install marker go only when every copy beside them goes too: a kept, edited
|
|
1399
1419
|
// devlyn-ideate needs both.
|
|
@@ -1412,6 +1432,7 @@ function cleanupSkillRoot(base, root, current) {
|
|
|
1412
1432
|
for (const spelling of legacySkillPaths(root, old)) renamed.set(path.basename(spelling).toLowerCase(), current);
|
|
1413
1433
|
}
|
|
1414
1434
|
const deprecated = new Set([...deprecatedSkillPaths(root)].map((file) => path.basename(file).toLowerCase()));
|
|
1435
|
+
const optional = new Set(OPTIONAL_ADDONS.map((addon) => addon.name.toLowerCase()));
|
|
1415
1436
|
const support = [];
|
|
1416
1437
|
let kept = 0;
|
|
1417
1438
|
let marker = false;
|
|
@@ -1443,17 +1464,36 @@ function cleanupSkillRoot(base, root, current) {
|
|
|
1443
1464
|
}
|
|
1444
1465
|
if (renamed.has(lower)) {
|
|
1445
1466
|
// An optional skill under its pre-4.0 name: the installer moves it to its new name; cleanup keeps it.
|
|
1446
|
-
if (CLEANUP_CORE.includes(renamed.get(lower)))
|
|
1467
|
+
if (CLEANUP_CORE.includes(renamed.get(lower))) {
|
|
1468
|
+
const proven = historicalSkill(file);
|
|
1469
|
+
items.push({ kind: 'renamed-skill', path: file, base, action: proven ? 'remove' : 'skip',
|
|
1470
|
+
reasons: proven ? [] : [cleanupReason('modified-copy')] });
|
|
1471
|
+
if (!proven) kept++;
|
|
1472
|
+
}
|
|
1447
1473
|
else { items.push({ kind: 'renamed-skill', path: file, base, action: 'skip', reasons: [cleanupReason('optional-skill')] }); kept++; }
|
|
1448
1474
|
continue;
|
|
1449
1475
|
}
|
|
1450
|
-
if (deprecated.has(lower)) {
|
|
1451
|
-
|
|
1476
|
+
if (deprecated.has(lower)) {
|
|
1477
|
+
const proven = historicalSkill(file);
|
|
1478
|
+
items.push({ kind: 'retired-skill', path: file, base, action: proven ? 'remove' : 'skip',
|
|
1479
|
+
reasons: proven ? [] : [cleanupReason('modified-copy')] });
|
|
1480
|
+
if (!proven) kept++;
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
const proven = isShippedCopy(file, RETIRED_SKILL_MD_SHA256[lower]);
|
|
1484
|
+
// Under a current optional add-on's name, other bytes are that add-on, installed by choice.
|
|
1485
|
+
if (!proven && optional.has(lower)) continue;
|
|
1486
|
+
items.push({ kind: 'retired-standard', path: file, base, action: proven ? 'remove' : 'skip',
|
|
1487
|
+
reasons: proven ? [] : [cleanupReason('modified-copy')] });
|
|
1488
|
+
if (!proven) kept++;
|
|
1452
1489
|
}
|
|
1453
1490
|
if (kept === 0) {
|
|
1454
1491
|
items.push(...support);
|
|
1455
1492
|
if (marker && items.some((item) => item.action === 'remove')) {
|
|
1456
|
-
|
|
1493
|
+
const file = path.join(root, DEVLYN_INSTALL_MARKER);
|
|
1494
|
+
const proven = cleanupMarkerOwned(file, current);
|
|
1495
|
+
items.push({ kind: 'install-marker', path: file, base, action: proven ? 'remove' : 'skip',
|
|
1496
|
+
reasons: proven ? [] : [cleanupReason('modified-copy')] });
|
|
1457
1497
|
}
|
|
1458
1498
|
}
|
|
1459
1499
|
if (cleanupInUse(root)) {
|
|
@@ -1468,7 +1508,12 @@ function cleanupCommands(base) {
|
|
|
1468
1508
|
const names = new Set(DEPRECATED_FILES.map((file) => path.basename(file).toLowerCase()));
|
|
1469
1509
|
return fs.readdirSync(commands).sort().filter((name) => names.has(name.toLowerCase())
|
|
1470
1510
|
&& fs.lstatSync(path.join(commands, name)).isFile())
|
|
1471
|
-
.map((name) =>
|
|
1511
|
+
.map((name) => {
|
|
1512
|
+
const file = path.join(commands, name);
|
|
1513
|
+
const proven = CLEANUP_HISTORY.commands[name.toLowerCase()]?.includes(contentHash(file));
|
|
1514
|
+
return { kind: 'legacy-command', path: file, base, action: proven ? 'remove' : 'skip',
|
|
1515
|
+
reasons: proven ? [] : [cleanupReason('modified-copy')] };
|
|
1516
|
+
});
|
|
1472
1517
|
}
|
|
1473
1518
|
|
|
1474
1519
|
function cleanupProjectFiles(base) {
|
|
@@ -1536,17 +1581,25 @@ function cleanupPython() {
|
|
|
1536
1581
|
|
|
1537
1582
|
// The GitHub repository and base branch a project's tracked changes are delivered to, or null.
|
|
1538
1583
|
function cleanupRemote(root) {
|
|
1539
|
-
const
|
|
1584
|
+
const { fail } = require('./release');
|
|
1585
|
+
const query = (args, absent) => {
|
|
1586
|
+
const result = cleanupGit(root, args);
|
|
1587
|
+
if (result.error || (result.status !== 0 && result.status !== absent)) {
|
|
1588
|
+
fail('repository-unreadable', { detail: result.error?.message || result.stderr.trim() });
|
|
1589
|
+
}
|
|
1590
|
+
return result;
|
|
1591
|
+
};
|
|
1592
|
+
const url = query(['remote', 'get-url', 'origin'], 2);
|
|
1540
1593
|
if (url.status !== 0) return null;
|
|
1541
1594
|
const match = url.stdout.trim().match(/github\.com[:/]([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
|
|
1542
1595
|
if (!match) return null;
|
|
1543
|
-
const head =
|
|
1544
|
-
//
|
|
1545
|
-
//
|
|
1596
|
+
const head = query(['symbolic-ref', '--quiet', '--short', 'refs/remotes/origin/HEAD'], 1);
|
|
1597
|
+
// With no default ref, the reviewed base is an existing main/master tracking branch or main.
|
|
1598
|
+
// Delivery uses exactly this choice; completion refuses a base that is not the remote default.
|
|
1546
1599
|
const base = head.status === 0 ? head.stdout.trim().replace(/^origin\//, '')
|
|
1547
|
-
: ['main', 'master'].find((name) =>
|
|
1600
|
+
: ['main', 'master'].find((name) => query(['rev-parse', '--verify', '--quiet', `refs/remotes/origin/${name}`], 1).status === 0)
|
|
1548
1601
|
|| 'main';
|
|
1549
|
-
return { repository: `${match[1]}/${match[2]}`, base
|
|
1602
|
+
return { repository: `${match[1]}/${match[2]}`, base };
|
|
1550
1603
|
}
|
|
1551
1604
|
|
|
1552
1605
|
// Whether a task is in flight on the installed copies: an allocated task's packet runs their exact
|
|
@@ -1569,6 +1622,31 @@ function cleanupSameFile(a, b) {
|
|
|
1569
1622
|
return Boolean(x && y && x.dev === y.dev && x.ino === y.ino);
|
|
1570
1623
|
}
|
|
1571
1624
|
|
|
1625
|
+
// Ownership is unknown if Git cannot read the work tree or index. Only an explicit non-repository
|
|
1626
|
+
// result or a successful empty index query permits local deletion, including machine/dotfiles roots.
|
|
1627
|
+
function cleanupTracked(file) {
|
|
1628
|
+
const root = path.dirname(file);
|
|
1629
|
+
const repo = cleanupGit(root, ['rev-parse', '--show-toplevel']);
|
|
1630
|
+
if (!repo.error && repo.status === 128 && repo.stderr.includes('not a git repository')) return false;
|
|
1631
|
+
if (repo.error || repo.status !== 0) throw new Error(repo.error?.message || repo.stderr.trim());
|
|
1632
|
+
const files = cleanupGit(root, ['ls-files', '-z', '--', `:(icase,literal)${file}`]);
|
|
1633
|
+
if (files.error || files.status !== 0) throw new Error(files.error?.message || files.stderr.trim());
|
|
1634
|
+
return files.stdout.length > 0;
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// A tracked copy remains in the caller's checkout even after PR delivery. Decide retention across
|
|
1638
|
+
// the whole root, before splitting routes; support in the delivery tree is considered separately.
|
|
1639
|
+
function cleanupRetainSupport(items) {
|
|
1640
|
+
const remaining = new Set(items.filter((item) => !cleanupSupport(item)
|
|
1641
|
+
&& (item.action === 'skip' || item.route === 'delivery')).map((item) => path.dirname(item.path)));
|
|
1642
|
+
for (const item of items) {
|
|
1643
|
+
if (cleanupSupport(item) && item.route === 'local' && item.action !== 'skip' && remaining.has(path.dirname(item.path))) {
|
|
1644
|
+
Object.assign(item, { action: 'skip', reasons: [cleanupReason('dependent-copy')] });
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
return items;
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1572
1650
|
function cleanupProjectScope(project, current) {
|
|
1573
1651
|
const { repositoryIdentity } = require('./harness-status');
|
|
1574
1652
|
const { physicalRoot } = require('./release');
|
|
@@ -1588,7 +1666,9 @@ function cleanupProjectScope(project, current) {
|
|
|
1588
1666
|
for (const item of items) {
|
|
1589
1667
|
if (busy && item.action !== 'skip') Object.assign(item, { action: 'skip', reasons: [busy] });
|
|
1590
1668
|
const relative = path.relative(base, item.path).split(path.sep).join('/');
|
|
1591
|
-
|
|
1669
|
+
let tracked = false;
|
|
1670
|
+
try { tracked = cleanupTracked(item.path); }
|
|
1671
|
+
catch (error) { Object.assign(item, { action: 'skip', reasons: [cleanupReason('repository-unreadable', { detail: error.message })] }); }
|
|
1592
1672
|
scope.items.push({ ...item, relative, route: tracked ? 'delivery' : 'local' });
|
|
1593
1673
|
}
|
|
1594
1674
|
if (!scope.remote) {
|
|
@@ -1596,6 +1676,7 @@ function cleanupProjectScope(project, current) {
|
|
|
1596
1676
|
if (item.route === 'delivery' && item.action !== 'skip') Object.assign(item, { action: 'skip', reasons: [cleanupReason('no-remote')] });
|
|
1597
1677
|
}
|
|
1598
1678
|
}
|
|
1679
|
+
cleanupRetainSupport(scope.items);
|
|
1599
1680
|
return scope;
|
|
1600
1681
|
}
|
|
1601
1682
|
|
|
@@ -1610,7 +1691,15 @@ function cleanupMachineScope(home, accountHomes, current) {
|
|
|
1610
1691
|
items.push(...cleanupSkillRoot(base, root, current));
|
|
1611
1692
|
}
|
|
1612
1693
|
items.push(...cleanupCommands(home));
|
|
1613
|
-
|
|
1694
|
+
for (const item of items) {
|
|
1695
|
+
try {
|
|
1696
|
+
if (cleanupTracked(item.path)) Object.assign(item, { action: 'skip', reasons: [cleanupReason('tracked-copy')] });
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
Object.assign(item, { action: 'skip', reasons: [cleanupReason('repository-unreadable', { detail: error.message })] });
|
|
1699
|
+
}
|
|
1700
|
+
Object.assign(item, { relative: null, route: 'local' });
|
|
1701
|
+
}
|
|
1702
|
+
return { kind: 'machine', root: home, name: null, remote: null, items: cleanupRetainSupport(items) };
|
|
1614
1703
|
}
|
|
1615
1704
|
|
|
1616
1705
|
function cleanupScopes(inputs, current) {
|
|
@@ -1622,7 +1711,7 @@ function cleanupScopes(inputs, current) {
|
|
|
1622
1711
|
try { scope = cleanupProjectScope(project, current); }
|
|
1623
1712
|
catch (error) {
|
|
1624
1713
|
scope = { kind: 'project', root: project, name: path.basename(project), remote: null, items: [{ kind: 'project', path: project, base: project,
|
|
1625
|
-
relative: '', route: 'local', action: 'skip', reasons: [cleanupReason('project-unreadable', { detail: error.message })] }] };
|
|
1714
|
+
relative: '', route: 'local', action: 'skip', reasons: [cleanupReason(error.reason?.code === 'repository-unreadable' ? 'repository-unreadable' : 'project-unreadable', { detail: error.message })] }] };
|
|
1626
1715
|
}
|
|
1627
1716
|
scopes.push({ id: `project:${sha256(Buffer.from(project)).slice(0, 12)}`, ...scope });
|
|
1628
1717
|
}
|
|
@@ -1637,14 +1726,18 @@ function cleanupPlan(inputs) {
|
|
|
1637
1726
|
return { ...plan, digest: cleanupDigest(plan) };
|
|
1638
1727
|
}
|
|
1639
1728
|
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1729
|
+
// JSON text with sorted keys: a plan the app stored with its own key order still reads the same.
|
|
1730
|
+
function cleanupCanonical(value) {
|
|
1731
|
+
return Array.isArray(value) ? `[${value.map(cleanupCanonical).join(',')}]`
|
|
1643
1732
|
: value && typeof value === 'object'
|
|
1644
|
-
? `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${
|
|
1733
|
+
? `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${cleanupCanonical(value[key])}`).join(',')}}`
|
|
1645
1734
|
: JSON.stringify(value);
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
function cleanupDigest(plan) {
|
|
1738
|
+
const { sha256 } = require('./release');
|
|
1646
1739
|
const { digest, ...body } = plan;
|
|
1647
|
-
return sha256(Buffer.from(
|
|
1740
|
+
return sha256(Buffer.from(cleanupCanonical(body)));
|
|
1648
1741
|
}
|
|
1649
1742
|
|
|
1650
1743
|
// Apply one item a fresh plan proves right now; returns a reason when it is not what was proven.
|
|
@@ -1695,6 +1788,11 @@ function cleanupLocal(scope, items) {
|
|
|
1695
1788
|
const roots = [...new Set(items.map((item) => path.dirname(item.path)).filter((dir) => path.basename(dir) === 'skills'
|
|
1696
1789
|
&& fs.lstatSync(dir, { throwIfNoEntry: false })?.isDirectory()))];
|
|
1697
1790
|
const act = (item) => {
|
|
1791
|
+
try {
|
|
1792
|
+
if (cleanupTracked(item.path)) { results.set(item.id, cleanupReason('tracked-copy')); return; }
|
|
1793
|
+
} catch (error) {
|
|
1794
|
+
results.set(item.id, cleanupReason('repository-unreadable', { detail: error.message })); return;
|
|
1795
|
+
}
|
|
1698
1796
|
try { results.set(item.id, cleanupApplyItem(item)); }
|
|
1699
1797
|
catch (error) { results.set(item.id, cleanupReason('apply-failed', { detail: error.message })); }
|
|
1700
1798
|
};
|
|
@@ -1729,36 +1827,54 @@ function cleanupDeliver(scope, items, current) {
|
|
|
1729
1827
|
if (!python) return { error: cleanupReason('requirement-missing', { name: 'Python 3.11' }) };
|
|
1730
1828
|
const run = (args, cwd) => spawnSync(python, ['-B', helper, ...args], { cwd, encoding: 'utf8', env: { ...process.env, PYTHONDONTWRITEBYTECODE: '1' } });
|
|
1731
1829
|
const lastJson = (text) => { try { return JSON.parse(text.trim().split('\n').at(-1)); } catch { return null; } };
|
|
1732
|
-
// Without origin/HEAD the base was a guess: GitHub names the default branch before anything is committed.
|
|
1733
|
-
let base = scope.remote.base;
|
|
1734
|
-
if (!scope.remote.named) {
|
|
1735
|
-
const asked = spawnSync('gh', ['repo', 'view', scope.remote.repository, '--json', 'defaultBranchRef', '-q', '.defaultBranchRef.name'],
|
|
1736
|
-
{ encoding: 'utf8', timeout: 30000 });
|
|
1737
|
-
if (asked.status === 0 && asked.stdout.trim()) base = asked.stdout.trim();
|
|
1738
|
-
}
|
|
1739
1830
|
const short = crypto.randomBytes(4).toString('hex');
|
|
1740
1831
|
const branch = `devlyn/cleanup-${short}`;
|
|
1741
1832
|
const worktree = path.join(path.dirname(scope.root), `${path.basename(scope.root)}.devlyn.noindex`, `devlyn-cleanup-${short}`);
|
|
1742
1833
|
const allocated = lastJson(run(['allocate', '--repo', scope.root, '--task', `devlyn-cli ${PKG.version} legacy cleanup`,
|
|
1743
1834
|
'--branch', branch, '--worktree', worktree, '--repository', scope.remote.repository,
|
|
1744
|
-
'--remote', 'origin', '--base', base], scope.root).stdout);
|
|
1835
|
+
'--remote', 'origin', '--base', scope.remote.base], scope.root).stdout);
|
|
1745
1836
|
if (allocated?.status !== 'ALLOCATED') return { error: cleanupReason('delivery-failed', { detail: allocated?.reason || 'allocation failed' }) };
|
|
1746
1837
|
const release = (error) => {
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1838
|
+
for (const args of [['worktree', 'remove', '--force', worktree], ['branch', '-D', branch]]) {
|
|
1839
|
+
const result = cleanupGit(scope.root, args);
|
|
1840
|
+
if (result.error || result.status !== 0) return { error: cleanupReason(error.code, { ...error.params,
|
|
1841
|
+
releaseError: result.error?.message || result.stderr.trim(), worktree, receipt: allocated.receipt }) };
|
|
1842
|
+
}
|
|
1843
|
+
fs.rmSync(path.dirname(allocated.receipt), { recursive: true, force: true });
|
|
1750
1844
|
return { error };
|
|
1751
1845
|
};
|
|
1846
|
+
let prepared;
|
|
1847
|
+
try { prepared = cleanupDeliveryCommit(worktree, items, current, release); }
|
|
1848
|
+
catch (error) { return release(cleanupReason('apply-failed', { detail: error.message })); }
|
|
1849
|
+
if (prepared.error) return prepared;
|
|
1850
|
+
const completed = lastJson(run(['complete', '--receipt', allocated.receipt, '--acceptance', prepared.acceptance, '--writers-stopped'], scope.root).stdout);
|
|
1851
|
+
const status = completed?.delivery_status || completed?.status;
|
|
1852
|
+
if (status === 'COMPLETE' || status === 'MERGED') return { delivery: { state: 'merged', url: completed.pr || null } };
|
|
1853
|
+
if (completed?.pr) return { delivery: { state: 'pr-open', url: completed.pr } };
|
|
1854
|
+
const reason = completed?.reason || 'delivery did not complete';
|
|
1855
|
+
const auth = /\b(authentication|unauthorized|gh auth login|could not read Username|HTTP 40[13])\b/i.test(reason);
|
|
1856
|
+
return { error: cleanupReason(auth ? 'auth-failed' : 'delivery-failed', { detail: reason, ...(completed?.resume ? { resume: completed.resume } : {}) }) };
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
// Inside the allocated worktree: prove every reviewed item, remove them, commit, and write the
|
|
1860
|
+
// acceptance completion binds. A failure releases the worktree; so does a throw, in the caller.
|
|
1861
|
+
function cleanupDeliveryCommit(worktree, items, current, release) {
|
|
1752
1862
|
const fresh = cleanupProjectScope(worktree, current);
|
|
1753
|
-
const
|
|
1863
|
+
const available = new Map(fresh.items.map((item) => [`${item.kind}\u0000${item.relative}`, item]));
|
|
1864
|
+
const pairs = items.map((reviewed) => {
|
|
1865
|
+
const fresh = available.get(`${reviewed.kind}\u0000${reviewed.relative}`);
|
|
1866
|
+
return { reviewed, fresh, before: fresh && cleanupPreimage(fresh.path) };
|
|
1867
|
+
});
|
|
1868
|
+
const stale = pairs.filter(({ reviewed, fresh, before }) => !fresh || fresh.action !== reviewed.action || fresh.route !== 'delivery'
|
|
1869
|
+
|| before.type !== reviewed.before.type || before.sha256 !== reviewed.before.sha256);
|
|
1870
|
+
// Check the entire batch before mutating even one item; the remote base cannot replace the reviewed preimage.
|
|
1871
|
+
if (stale.length) return release(cleanupReason('stale-plan', { detail: stale.map(({ reviewed }) => reviewed.relative).join(', ') }));
|
|
1754
1872
|
const applied = [];
|
|
1755
|
-
for (const
|
|
1756
|
-
const
|
|
1757
|
-
if (
|
|
1758
|
-
|
|
1873
|
+
for (const { reviewed, fresh } of pairs) {
|
|
1874
|
+
const error = cleanupApplyItem({ ...fresh, before: reviewed.before });
|
|
1875
|
+
if (error) return release(cleanupReason(error.code, { ...error.params, detail: reviewed.relative }));
|
|
1876
|
+
applied.push(fresh.relative);
|
|
1759
1877
|
}
|
|
1760
|
-
// A reviewed item the remote base does not hold as reviewed (not pushed, already gone) is not delivered.
|
|
1761
|
-
if (wanted.size) return release(cleanupReason('stale-plan', { detail: [...wanted.values()].map((item) => item.relative).join(', ') }));
|
|
1762
1878
|
for (const dir of ['.agents/skills', '.claude/skills']) {
|
|
1763
1879
|
try { const full = path.join(worktree, dir); if (fs.readdirSync(full).length === 0) fs.rmdirSync(full); } catch {}
|
|
1764
1880
|
}
|
|
@@ -1774,17 +1890,13 @@ function cleanupDeliver(scope, items, current) {
|
|
|
1774
1890
|
try { left = JSON.parse(check.stdout.trim().split('\n').at(-1)).data.scopes[0].items.filter((item) => item.action !== 'skip' && applied.includes(item.relative)); }
|
|
1775
1891
|
catch { return release(cleanupReason('delivery-failed', { detail: 'the check after cleanup could not run' })); }
|
|
1776
1892
|
if (left.length) return release(cleanupReason('delivery-failed', { detail: 'items remain after cleanup' }));
|
|
1893
|
+
const head = cleanupGit(worktree, ['rev-parse', 'HEAD']);
|
|
1894
|
+
if (head.error || head.status !== 0) return release(cleanupReason('repository-unreadable', { detail: head.error?.message || head.stderr.trim() }));
|
|
1777
1895
|
const acceptance = path.join(worktree, '.devlyn', 'acceptance.json');
|
|
1778
1896
|
fs.writeFileSync(acceptance, JSON.stringify({ kind: 'direct', task: `devlyn-cli ${PKG.version} legacy cleanup`,
|
|
1779
|
-
source_sha:
|
|
1897
|
+
source_sha: head.stdout.trim(),
|
|
1780
1898
|
checks: [{ command: `devlyn cleanup --plan --project ${worktree} --json`, evidence: '.devlyn/cleanup-check.json' }] }));
|
|
1781
|
-
|
|
1782
|
-
const status = completed?.delivery_status || completed?.status;
|
|
1783
|
-
if (status === 'COMPLETE' || status === 'MERGED') return { delivery: { state: 'merged', url: completed.pr || null } };
|
|
1784
|
-
if (completed?.pr) return { delivery: { state: 'pr-open', url: completed.pr } };
|
|
1785
|
-
const reason = completed?.reason || 'delivery did not complete';
|
|
1786
|
-
const auth = /\b(authentication|unauthorized|gh auth login|could not read Username|HTTP 40[13])\b/i.test(reason);
|
|
1787
|
-
return { error: cleanupReason(auth ? 'auth-failed' : 'delivery-failed', { detail: reason, ...(completed?.resume ? { resume: completed.resume } : {}) }) };
|
|
1899
|
+
return { acceptance };
|
|
1788
1900
|
}
|
|
1789
1901
|
|
|
1790
1902
|
function cleanupApply(plan, { events = false } = {}) {
|
|
@@ -1799,11 +1911,23 @@ function cleanupApply(plan, { events = false } = {}) {
|
|
|
1799
1911
|
if (!pairs.length) { scopes.push({ ...progress, state: 'skipped' }); emit(scopes.at(-1)); continue; }
|
|
1800
1912
|
emit(progress);
|
|
1801
1913
|
try {
|
|
1914
|
+
const observed = fresh.get(reviewed.id);
|
|
1915
|
+
if (reviewed.kind !== observed?.kind
|
|
1916
|
+
|| (observed.kind === 'project' && cleanupCanonical(reviewed.remote) !== cleanupCanonical(observed.remote))) {
|
|
1917
|
+
Object.assign(progress, { state: 'failed', error: cleanupReason('stale-plan', { detail: 'delivery identity changed' }) });
|
|
1918
|
+
scopes.push(progress); emit(progress); continue;
|
|
1919
|
+
}
|
|
1802
1920
|
const refused = pairs.find((pair) => !pair.fresh);
|
|
1803
|
-
|
|
1921
|
+
// A reviewed copy refused now stays on disk, so the support beside it stays too.
|
|
1922
|
+
const held = new Set(pairs.filter((pair) => !pair.fresh && !cleanupSupport(pair.reviewed))
|
|
1923
|
+
.map((pair) => path.dirname(pair.reviewed.path)));
|
|
1924
|
+
const proven = pairs.filter((pair) => pair.fresh && !(cleanupSupport(pair.fresh) && held.has(path.dirname(pair.fresh.path))))
|
|
1925
|
+
.map((pair) => pair.fresh);
|
|
1804
1926
|
const local = cleanupLocal(fresh.get(reviewed.id), proven.filter((item) => item.route === 'local'));
|
|
1805
1927
|
const failedLocal = [...local.values()].find((reason) => reason);
|
|
1806
|
-
|
|
1928
|
+
// A local copy that could not go keeps the tracked support beside it, as cleanupLocal keeps local support.
|
|
1929
|
+
const keptRoots = new Set(proven.filter((item) => item.route === 'local' && local.get(item.id)).map((item) => path.dirname(item.path)));
|
|
1930
|
+
const deliverable = proven.filter((item) => item.route === 'delivery' && !(cleanupSupport(item) && keptRoots.has(path.dirname(item.path))));
|
|
1807
1931
|
const delivery = deliverable.length ? cleanupDeliver(fresh.get(reviewed.id), deliverable, current) : null;
|
|
1808
1932
|
progress.delivery = delivery?.delivery || null;
|
|
1809
1933
|
const error = delivery?.error || failedLocal || (refused && cleanupReason('stale-plan'));
|
package/bin/skill-history.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"source": "skills: config/skills/* and optional-skills/* trees from every first-parent HEAD commit touching those paths and every
|
|
2
|
+
"source": "skills: config/skills/* and optional-skills/* trees from every first-parent HEAD commit touching those paths and every release tag, derived by scripts/update-skill-history.js using bin/skill-ownership.js normalization (LF, install-path stamps, OS metadata and Python bytecode)",
|
|
3
3
|
"skills": {
|
|
4
4
|
"_shared": [
|
|
5
5
|
"015eea12cc07cfb9eb318c30855f6deacb5bf43bd508a511dffd2e48c9d82964",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"69a3fa5c1e7c5358cf7571936c8c09f5cc1d13999c8490ac10533af2091c4226",
|
|
81
81
|
"6a523474b70ba99aff5472020cdde9ca1e7c91f8ee65db03c9e712adfac6d31d",
|
|
82
82
|
"6d1a5458b23196b6f6fde6b7e9dc097ed173cb5914de439e512a032ca1892812",
|
|
83
|
+
"6e70044cab5a3fb1f78b7148c2111e4298ce8f5f8e902220430ca316bb61255d",
|
|
83
84
|
"6fa2b5e089dcd00613c6e9800d2e763da9e5b2c80e6a7ef5d54d5ad7113e8399",
|
|
84
85
|
"71fb125e20420d6f3fcd3396093fcdc8d552bab9626c3cbd962351e0d19adac8",
|
|
85
86
|
"725395f70eec7e2ceae1f3aa122893433923b098a1e0d6202dd410979ef4eea0",
|
|
@@ -88,6 +89,7 @@
|
|
|
88
89
|
"7403aa2911ac89180c4abe2c15e4037a8023af134e2fe70ed35ae1b14c92a04c",
|
|
89
90
|
"74ad540635331051e8a32b6d7aa08c1926b9433298d1a1f699efe294b0c96d53",
|
|
90
91
|
"74d650102836e72f0ab6cec05760ced6e2e047c62c1fe403837879e91d2e1cc4",
|
|
92
|
+
"75578439ec7edad038ff429071b7625f1bc1fc81cf6a715b3d4f2c05afa0e07e",
|
|
91
93
|
"75cdbe50f909866a69cd7f7fa6ed104f471a6386e19fcf82e695c001edeafe13",
|
|
92
94
|
"7a0f6c692bd1f74759d44f1a145899c157e1787635db546ea8a192461421e353",
|
|
93
95
|
"7ca704d818bf9c044fed59fdd4107bd0463f9b0514d44729003e3e52aa796353",
|
|
@@ -146,6 +148,7 @@
|
|
|
146
148
|
"d3b40b9f0e15bf90ca557c0198a8fe5ff1d046b9aaa54b181c743d8e006f9c34",
|
|
147
149
|
"d424feceffc005f220e33a19cac73f33bd40d120cc14da33725b8a990704bcf6",
|
|
148
150
|
"d7388e36d76647a89ce52de615271928cf9fc78f980df399d2bc2e6d6675ec82",
|
|
151
|
+
"d85fa24b9b2c439742c9a62d5d4e07a031a1cfe6024bbc06f98eff7063424179",
|
|
149
152
|
"dac3da11ea5686382f5c9892a0b8e4ec25e625e5946283d8e2ad944aa84e1df2",
|
|
150
153
|
"db867d1fd98058bb20e41e942abd95f08fbaa9465928025f69f0a0308fa71514",
|
|
151
154
|
"dd0bfdfd2db0b5f9ddef75d1b9ce3247698739fc551d292366dccd506fef6a36",
|
|
@@ -320,6 +323,7 @@
|
|
|
320
323
|
"0dc69c20434966015176d83baa6f95b3421f9e13d0b1bd608a22f0560eae1049",
|
|
321
324
|
"0fe9b270d670f70da9d2535969b6117684b7c1517806b3029ebaf9624da8006c",
|
|
322
325
|
"1582c202d361af927c2d0843d4bcb72ccc98a0a2a9d8344d118d680f70f7e1e4",
|
|
326
|
+
"19207315f23c9a12bf2ced0e4e79bb7dfdc7cc5d0c9dd94095d9c6dfe1e8fea8",
|
|
323
327
|
"1d8c3bd31c8b755acd0c3c9f416acb5024dfa99fbf5e2c14d8d6df00a24fe9b4",
|
|
324
328
|
"300d1af60cc53285935dc4b5b370e157707218d844a4303b96d7826eef36ed43",
|
|
325
329
|
"302b1a6c109087dce02ed6af184d6aeeca6cecfd40ebe638beecf8bdf21cf59c",
|
|
@@ -632,5 +636,132 @@
|
|
|
632
636
|
"711cd2633df2062104f12d50037a07a1012a90ab18c422cb5fdf665fcb9dc987",
|
|
633
637
|
"d6a98c105a0d1952de90c39ca0734cb70cbdd116e6b4a4d45507d796bd2a9e88"
|
|
634
638
|
]
|
|
635
|
-
}
|
|
639
|
+
},
|
|
640
|
+
"versions": [
|
|
641
|
+
"0.2.1",
|
|
642
|
+
"0.3.0",
|
|
643
|
+
"0.4.0",
|
|
644
|
+
"0.5.0",
|
|
645
|
+
"0.5.1",
|
|
646
|
+
"0.5.2",
|
|
647
|
+
"0.5.3",
|
|
648
|
+
"0.5.4",
|
|
649
|
+
"0.5.5",
|
|
650
|
+
"0.5.7",
|
|
651
|
+
"0.5.8",
|
|
652
|
+
"0.6.0",
|
|
653
|
+
"0.7.0",
|
|
654
|
+
"0.7.1",
|
|
655
|
+
"0.7.2",
|
|
656
|
+
"1.0.0",
|
|
657
|
+
"1.0.1",
|
|
658
|
+
"1.1.0",
|
|
659
|
+
"1.1.1",
|
|
660
|
+
"1.1.2",
|
|
661
|
+
"1.10.0",
|
|
662
|
+
"1.11.0",
|
|
663
|
+
"1.12.0",
|
|
664
|
+
"1.12.1",
|
|
665
|
+
"1.12.2",
|
|
666
|
+
"1.12.3",
|
|
667
|
+
"1.12.4",
|
|
668
|
+
"1.12.5",
|
|
669
|
+
"1.13.0",
|
|
670
|
+
"1.14.0",
|
|
671
|
+
"1.15.0",
|
|
672
|
+
"1.2.0",
|
|
673
|
+
"1.2.1",
|
|
674
|
+
"1.3.0",
|
|
675
|
+
"1.3.1",
|
|
676
|
+
"1.3.2",
|
|
677
|
+
"1.4.0",
|
|
678
|
+
"1.5.0",
|
|
679
|
+
"1.5.1",
|
|
680
|
+
"1.5.2",
|
|
681
|
+
"1.5.3",
|
|
682
|
+
"1.5.4",
|
|
683
|
+
"1.6.0",
|
|
684
|
+
"1.7.0",
|
|
685
|
+
"1.7.1",
|
|
686
|
+
"1.7.2",
|
|
687
|
+
"1.7.3",
|
|
688
|
+
"1.8.0",
|
|
689
|
+
"1.8.1",
|
|
690
|
+
"1.9.0",
|
|
691
|
+
"1.9.1",
|
|
692
|
+
"2.0.0",
|
|
693
|
+
"2.1.0",
|
|
694
|
+
"2.10.0",
|
|
695
|
+
"2.10.1",
|
|
696
|
+
"2.2.0",
|
|
697
|
+
"2.2.1",
|
|
698
|
+
"2.2.2",
|
|
699
|
+
"2.3.0",
|
|
700
|
+
"2.3.1",
|
|
701
|
+
"2.3.2",
|
|
702
|
+
"2.4.0",
|
|
703
|
+
"2.4.1",
|
|
704
|
+
"2.5.0",
|
|
705
|
+
"2.5.1",
|
|
706
|
+
"2.5.2",
|
|
707
|
+
"2.5.3",
|
|
708
|
+
"2.5.4",
|
|
709
|
+
"2.5.5",
|
|
710
|
+
"2.5.6",
|
|
711
|
+
"2.5.7",
|
|
712
|
+
"2.6.0",
|
|
713
|
+
"2.6.1",
|
|
714
|
+
"2.7.0",
|
|
715
|
+
"2.8.0",
|
|
716
|
+
"2.8.1",
|
|
717
|
+
"2.9.0",
|
|
718
|
+
"2.9.1",
|
|
719
|
+
"2.9.2",
|
|
720
|
+
"2.9.3",
|
|
721
|
+
"3.0.0",
|
|
722
|
+
"3.0.1",
|
|
723
|
+
"3.1.0",
|
|
724
|
+
"3.1.1",
|
|
725
|
+
"3.1.2",
|
|
726
|
+
"3.1.3",
|
|
727
|
+
"3.2.0",
|
|
728
|
+
"3.2.1",
|
|
729
|
+
"3.3.0",
|
|
730
|
+
"3.3.1",
|
|
731
|
+
"4.0.0",
|
|
732
|
+
"4.0.1",
|
|
733
|
+
"4.1.0",
|
|
734
|
+
"4.2.0",
|
|
735
|
+
"4.2.1",
|
|
736
|
+
"4.2.10",
|
|
737
|
+
"4.2.11",
|
|
738
|
+
"4.2.12",
|
|
739
|
+
"4.2.13",
|
|
740
|
+
"4.2.14",
|
|
741
|
+
"4.2.15",
|
|
742
|
+
"4.2.16",
|
|
743
|
+
"4.2.17",
|
|
744
|
+
"4.2.18",
|
|
745
|
+
"4.2.19",
|
|
746
|
+
"4.2.2",
|
|
747
|
+
"4.2.20",
|
|
748
|
+
"4.2.21",
|
|
749
|
+
"4.2.22",
|
|
750
|
+
"4.2.23",
|
|
751
|
+
"4.2.24",
|
|
752
|
+
"4.2.25",
|
|
753
|
+
"4.2.26",
|
|
754
|
+
"4.2.3",
|
|
755
|
+
"4.2.4",
|
|
756
|
+
"4.2.5",
|
|
757
|
+
"4.2.6",
|
|
758
|
+
"4.2.7",
|
|
759
|
+
"4.2.8",
|
|
760
|
+
"4.2.9",
|
|
761
|
+
"4.3.0",
|
|
762
|
+
"4.3.1",
|
|
763
|
+
"4.3.2",
|
|
764
|
+
"4.3.3",
|
|
765
|
+
"4.3.4"
|
|
766
|
+
]
|
|
636
767
|
}
|
|
@@ -12,6 +12,7 @@ import json
|
|
|
12
12
|
import os
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
import re
|
|
15
|
+
import runpy
|
|
15
16
|
import subprocess
|
|
16
17
|
import sys
|
|
17
18
|
|
|
@@ -52,8 +53,13 @@ def release_ref(value):
|
|
|
52
53
|
|
|
53
54
|
def verify_release(root):
|
|
54
55
|
root = Path(root)
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
# As harness-status verifies it: an npm-installed package keeps npm's own .bin shims, which are checked.
|
|
57
|
+
script = "const r=require(process.argv[1]);r.runJson(()=>r.verifyRelease(process.argv[2],{npmInstall:true}));"
|
|
58
|
+
try:
|
|
59
|
+
node = runpy.run_path(str(Path(__file__).with_name("platform-support.py")))["managed_node"]() or "node"
|
|
60
|
+
result = subprocess.run([node, "-e", script, str(root / "bin/release.js"), str(root)], capture_output=True, text=True, encoding="utf-8")
|
|
61
|
+
except (OSError, ValueError) as exc:
|
|
62
|
+
raise HarnessError(f"release verifier could not start: {exc}") from exc
|
|
57
63
|
require(result.returncode == 0, f"release verification failed: {root}: {result.stderr.strip()}")
|
|
58
64
|
try:
|
|
59
65
|
release = release_ref(json.loads(result.stdout)["data"]["release"])
|