@agent-delivery-harness/action 0.1.0 → 0.2.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/package.json +8 -3
- package/src/main.ts +29 -8
package/package.json
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-delivery-harness/action",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "GitHub Action verifying a tracked delivery record on pull requests",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/kwam1na/agent-delivery-harness.git",
|
|
10
|
+
"directory": "packages/action"
|
|
11
|
+
},
|
|
7
12
|
"engines": {
|
|
8
|
-
"node": ">=22"
|
|
13
|
+
"node": ">=22.6.0"
|
|
9
14
|
},
|
|
10
15
|
"dependencies": {
|
|
11
|
-
"@agent-delivery-harness/kernel": "0.
|
|
16
|
+
"@agent-delivery-harness/kernel": "0.2.0"
|
|
12
17
|
},
|
|
13
18
|
"exports": {
|
|
14
19
|
".": "./src/index.ts"
|
package/src/main.ts
CHANGED
|
@@ -64,6 +64,8 @@ import {
|
|
|
64
64
|
createBlocker,
|
|
65
65
|
createInternalErrorBlocker,
|
|
66
66
|
deliveryRecordPathFor,
|
|
67
|
+
needsCommittedSymlinkTarget,
|
|
68
|
+
parseCandidateTreeListing,
|
|
67
69
|
parseDeliveryRecord,
|
|
68
70
|
renderBlockers,
|
|
69
71
|
runGitCommand,
|
|
@@ -73,6 +75,7 @@ import {
|
|
|
73
75
|
type Blocker,
|
|
74
76
|
type BlockerSource,
|
|
75
77
|
type CandidateCommandRunner,
|
|
78
|
+
type CandidateTreeEntry,
|
|
76
79
|
type DeliveryRecordCheck,
|
|
77
80
|
type DeliveryRecordClaim,
|
|
78
81
|
type DeliveryRecordFile,
|
|
@@ -170,9 +173,6 @@ export interface ActionResult {
|
|
|
170
173
|
|
|
171
174
|
// ── Blockers ─────────────────────────────────────────────────────────────────
|
|
172
175
|
|
|
173
|
-
/** `git ls-tree -z` separates records with NUL, so a path containing a newline stays one record. */
|
|
174
|
-
const NUL = "\u0000";
|
|
175
|
-
|
|
176
176
|
const ACTION_SOURCE: BlockerSource = { kind: "command", id: "delivery-harness.action" };
|
|
177
177
|
|
|
178
178
|
const RECORD_AND_COMMIT: Remediation = {
|
|
@@ -476,6 +476,13 @@ interface DiscoveredRecords {
|
|
|
476
476
|
/** Parse failures. Findings, never skips — see below. */
|
|
477
477
|
readonly blockers: readonly Blocker[];
|
|
478
478
|
readonly trackedCount: number;
|
|
479
|
+
/**
|
|
480
|
+
* Every tracked entry in the head tree, for the protected-path check —
|
|
481
|
+
* mode and, for a delivery-owned symlink, the target read from its committed
|
|
482
|
+
* blob, because the one admitted exception under `.claude/skills/` turns on
|
|
483
|
+
* both and neither is visible in a name-only listing.
|
|
484
|
+
*/
|
|
485
|
+
readonly allPaths: readonly CandidateTreeEntry[];
|
|
479
486
|
}
|
|
480
487
|
|
|
481
488
|
/**
|
|
@@ -503,11 +510,12 @@ async function readTrackedRecords(git: GitPort, headSha: string, config: Harness
|
|
|
503
510
|
// from a root-level path of that name — a path/content confusion. The two
|
|
504
511
|
// sides of this check must be anchored identically or they are not comparing
|
|
505
512
|
// the same tree.
|
|
506
|
-
const listing = await git(["ls-tree", "-r", "
|
|
513
|
+
const listing = await git(["ls-tree", "-r", "-z", "--full-tree", headSha]);
|
|
507
514
|
if (listing.exitCode !== 0) {
|
|
508
515
|
return {
|
|
509
516
|
files: [],
|
|
510
517
|
trackedCount: 0,
|
|
518
|
+
allPaths: [],
|
|
511
519
|
blockers: [
|
|
512
520
|
actionBlocker({
|
|
513
521
|
code: "tracked_tree_unreadable",
|
|
@@ -525,7 +533,20 @@ async function readTrackedRecords(git: GitPort, headSha: string, config: Harness
|
|
|
525
533
|
};
|
|
526
534
|
}
|
|
527
535
|
|
|
528
|
-
const
|
|
536
|
+
const listed = parseCandidateTreeListing(listing.stdout);
|
|
537
|
+
const allPaths: CandidateTreeEntry[] = [];
|
|
538
|
+
for (const entry of listed) {
|
|
539
|
+
if (!needsCommittedSymlinkTarget(entry)) {
|
|
540
|
+
allPaths.push(entry);
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
// Read out of the commit, like every other byte this verifier judges: the
|
|
544
|
+
// link under review is the one in the tree. A blob that will not read
|
|
545
|
+
// leaves the target absent, and an entry with no target cannot be admitted.
|
|
546
|
+
const link = await git(["cat-file", "blob", entry.objectSha]);
|
|
547
|
+
allPaths.push(link.exitCode === 0 ? { ...entry, symlinkTarget: link.stdout } : entry);
|
|
548
|
+
}
|
|
549
|
+
const paths = listed.map((entry) => entry.path).filter((entry) => isRecordPath(entry, shape));
|
|
529
550
|
const files: DeliveryRecordFile[] = [];
|
|
530
551
|
const blockers: Blocker[] = [];
|
|
531
552
|
for (const recordPath of paths.sort()) {
|
|
@@ -548,7 +569,7 @@ async function readTrackedRecords(git: GitPort, headSha: string, config: Harness
|
|
|
548
569
|
}
|
|
549
570
|
files.push({ path: recordPath, record: parsed.record });
|
|
550
571
|
}
|
|
551
|
-
return { files, blockers, trackedCount: paths.length };
|
|
572
|
+
return { files, blockers, trackedCount: paths.length, allPaths };
|
|
552
573
|
}
|
|
553
574
|
|
|
554
575
|
// ── The summary ──────────────────────────────────────────────────────────────
|
|
@@ -949,7 +970,7 @@ export async function runAction(runtime: ActionRuntime): Promise<ActionResult> {
|
|
|
949
970
|
}
|
|
950
971
|
|
|
951
972
|
recordPath = selected.path;
|
|
952
|
-
check = verifyDeliveryRecord(config, selected.record, identity, base);
|
|
973
|
+
check = verifyDeliveryRecord(config, selected.record, identity, base, { candidateTreePaths: discovered.allPaths });
|
|
953
974
|
blockers.push(...check.blockers);
|
|
954
975
|
return await settle();
|
|
955
976
|
} catch (error) {
|
|
@@ -990,7 +1011,7 @@ async function workspaceHas(runtime: ActionRuntime, repoRoot: string, relativePa
|
|
|
990
1011
|
*
|
|
991
1012
|
* The Action carries its own loader rather than importing the CLI's: the Action
|
|
992
1013
|
* is a wrapper over the *kernel*, and a dependency on the operator CLI would put
|
|
993
|
-
*
|
|
1014
|
+
* eleven commands and an interactive prompt behind a read-only check. The
|
|
994
1015
|
* validation itself is the kernel's single implementation; only the import and
|
|
995
1016
|
* the blocker's source id are local, so the two loaders cannot disagree about
|
|
996
1017
|
* what a valid config is.
|