@codyswann/lisa 4.60.12 → 4.61.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/dist/cli/starter-sync-content.d.ts +25 -0
- package/dist/cli/starter-sync-content.d.ts.map +1 -0
- package/dist/cli/starter-sync-content.js +106 -0
- package/dist/cli/starter-sync-content.js.map +1 -0
- package/dist/cli/starter-sync-source.d.ts +33 -0
- package/dist/cli/starter-sync-source.d.ts.map +1 -0
- package/dist/cli/starter-sync-source.js +154 -0
- package/dist/cli/starter-sync-source.js.map +1 -0
- package/dist/cli/starter-sync-write.d.ts +11 -0
- package/dist/cli/starter-sync-write.d.ts.map +1 -0
- package/dist/cli/starter-sync-write.js +126 -0
- package/dist/cli/starter-sync-write.js.map +1 -0
- package/dist/cli/starter-sync.d.ts +22 -0
- package/dist/cli/starter-sync.d.ts.map +1 -0
- package/dist/cli/starter-sync.js +219 -0
- package/dist/cli/starter-sync.js.map +1 -0
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/nightly-e2e-guard-behavior-certificate.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +9 -0
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/health/template-inspection.d.ts +16 -1
- package/dist/health/template-inspection.d.ts.map +1 -1
- package/dist/health/template-inspection.js +1 -1
- package/dist/health/template-inspection.js.map +1 -1
- package/dist/strategies/copy-contents.d.ts +11 -0
- package/dist/strategies/copy-contents.d.ts.map +1 -1
- package/dist/strategies/copy-contents.js +21 -5
- package/dist/strategies/copy-contents.js.map +1 -1
- package/dist/utils/atomic-file-write.d.ts +1 -1
- package/dist/utils/atomic-file-write.d.ts.map +1 -1
- package/dist/utils/atomic-file-write.js.map +1 -1
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { TemplateCandidate } from "../health/template-inspection.js";
|
|
2
|
+
import type { StarterChange } from "./starter-sync-source.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolve canonical markers; markerless copy-contents uses whole-file ownership.
|
|
5
|
+
* @param candidates - Applicable copy-contents template sources.
|
|
6
|
+
* @returns Distinct governed opening markers.
|
|
7
|
+
*/
|
|
8
|
+
export declare function starterBlockMarkers(candidates: readonly TemplateCandidate[]): Promise<readonly string[]>;
|
|
9
|
+
/**
|
|
10
|
+
* Apply only the blocks governed by the existing canonical template sources.
|
|
11
|
+
* @param change - Exact before/after starter file.
|
|
12
|
+
* @param current - Existing consumer bytes.
|
|
13
|
+
* @param markers - Canonical opening markers for this path.
|
|
14
|
+
* @returns Updated bytes, preserving all consumer content outside those blocks.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mergeStarterBlocks(change: StarterChange, current: Buffer | undefined, markers: readonly string[]): Promise<Buffer>;
|
|
17
|
+
/**
|
|
18
|
+
* Reuse Lisa's JSON strategies for paths governed by JSON merge semantics.
|
|
19
|
+
* @param change - Pinned starter JSON file.
|
|
20
|
+
* @param current - Existing consumer bytes.
|
|
21
|
+
* @param candidates - Applicable JSON strategies in canonical order.
|
|
22
|
+
* @returns Merged bytes; source removal preserves the consumer's JSON document.
|
|
23
|
+
*/
|
|
24
|
+
export declare function mergeStarterJson(change: StarterChange, current: Buffer | undefined, candidates: readonly TemplateCandidate[]): Buffer | undefined;
|
|
25
|
+
//# sourceMappingURL=starter-sync-content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-sync-content.d.ts","sourceRoot":"","sources":["../../src/cli/starter-sync-content.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAQ1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAyD9D;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,SAAS,iBAAiB,EAAE,GACvC,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAW5B;AAED;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,SAAS,MAAM,EAAE,GACzB,OAAO,CAAC,MAAM,CAAC,CAejB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,UAAU,EAAE,SAAS,iBAAiB,EAAE,GACvC,MAAM,GAAG,SAAS,CAiBpB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { findCopyContentsBlock, mergeCopyContents, } from "../strategies/copy-contents.js";
|
|
3
|
+
import { mergeTemplateJson } from "../strategies/merge.js";
|
|
4
|
+
import { TaggedMergeStrategy } from "../strategies/tagged-merge.js";
|
|
5
|
+
import { isJsonObject } from "../sync/json-path.js";
|
|
6
|
+
const BEGIN = "# BEGIN: AI GUARDRAILS";
|
|
7
|
+
const END = "# END: AI GUARDRAILS";
|
|
8
|
+
/**
|
|
9
|
+
* Decode managed text without silently replacing invalid bytes.
|
|
10
|
+
* @param bytes - File payload, if the path exists.
|
|
11
|
+
* @returns Strict UTF-8 content.
|
|
12
|
+
*/
|
|
13
|
+
function text(bytes) {
|
|
14
|
+
return bytes === undefined
|
|
15
|
+
? ""
|
|
16
|
+
: new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Extract one complete marker pair without importing outside starter content.
|
|
20
|
+
* @param content - Flat starter or consumer file.
|
|
21
|
+
* @param begin - Canonical Lisa template's opening marker.
|
|
22
|
+
* @returns Normalized managed block, or undefined when absent.
|
|
23
|
+
*/
|
|
24
|
+
function block(content, begin) {
|
|
25
|
+
const end = END + begin.slice(BEGIN.length);
|
|
26
|
+
const lines = content.split(/\r?\n/u);
|
|
27
|
+
const starts = lines.flatMap((line, index) => line === begin ? [index] : []);
|
|
28
|
+
const ends = lines.flatMap((line, index) => (line === end ? [index] : []));
|
|
29
|
+
if (starts.length === 0 && ends.length === 0)
|
|
30
|
+
return undefined;
|
|
31
|
+
if (starts.length !== 1 || ends.length !== 1 || starts[0] >= ends[0]) {
|
|
32
|
+
throw new Error("Incomplete or ambiguous starter managed block");
|
|
33
|
+
}
|
|
34
|
+
return lines.slice(starts[0], ends[0] + 1).join("\n");
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Replace or remove a verified managed block while retaining outside bytes.
|
|
38
|
+
* @param current - Consumer content.
|
|
39
|
+
* @param begin - Canonical opening marker.
|
|
40
|
+
* @param replacement - New block, or undefined for removal.
|
|
41
|
+
* @returns Updated content.
|
|
42
|
+
*/
|
|
43
|
+
function replaceBlock(current, begin, replacement) {
|
|
44
|
+
if (replacement !== undefined)
|
|
45
|
+
return mergeCopyContents(`${replacement}\n`, current);
|
|
46
|
+
const end = END + begin.slice(BEGIN.length);
|
|
47
|
+
const offsets = findCopyContentsBlock(current, begin, end);
|
|
48
|
+
return offsets === undefined
|
|
49
|
+
? current
|
|
50
|
+
: current.slice(0, offsets.start) + current.slice(offsets.end);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve canonical markers; markerless copy-contents uses whole-file ownership.
|
|
54
|
+
* @param candidates - Applicable copy-contents template sources.
|
|
55
|
+
* @returns Distinct governed opening markers.
|
|
56
|
+
*/
|
|
57
|
+
export async function starterBlockMarkers(candidates) {
|
|
58
|
+
const canonical = await Promise.all(candidates.map(candidate => readFile(candidate.source, "utf8")));
|
|
59
|
+
return [
|
|
60
|
+
...new Set(canonical.flatMap(source => source.split(/\r?\n/u).filter(line => line.startsWith(BEGIN)))),
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Apply only the blocks governed by the existing canonical template sources.
|
|
65
|
+
* @param change - Exact before/after starter file.
|
|
66
|
+
* @param current - Existing consumer bytes.
|
|
67
|
+
* @param markers - Canonical opening markers for this path.
|
|
68
|
+
* @returns Updated bytes, preserving all consumer content outside those blocks.
|
|
69
|
+
*/
|
|
70
|
+
export async function mergeStarterBlocks(change, current, markers) {
|
|
71
|
+
const before = text(change.before?.bytes);
|
|
72
|
+
const after = text(change.after?.bytes);
|
|
73
|
+
const merged = markers.reduce((content, begin) => {
|
|
74
|
+
const previous = block(before, begin);
|
|
75
|
+
const next = block(after, begin);
|
|
76
|
+
const existing = block(content, begin);
|
|
77
|
+
if (existing === next || previous === next)
|
|
78
|
+
return content;
|
|
79
|
+
if (existing !== undefined && existing !== previous)
|
|
80
|
+
throw new Error(`Starter managed block conflicts with local changes: ${change.path}`);
|
|
81
|
+
return replaceBlock(content, begin, next);
|
|
82
|
+
}, text(current));
|
|
83
|
+
return Buffer.from(merged);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Reuse Lisa's JSON strategies for paths governed by JSON merge semantics.
|
|
87
|
+
* @param change - Pinned starter JSON file.
|
|
88
|
+
* @param current - Existing consumer bytes.
|
|
89
|
+
* @param candidates - Applicable JSON strategies in canonical order.
|
|
90
|
+
* @returns Merged bytes; source removal preserves the consumer's JSON document.
|
|
91
|
+
*/
|
|
92
|
+
export function mergeStarterJson(change, current, candidates) {
|
|
93
|
+
if (change.after === undefined)
|
|
94
|
+
return current;
|
|
95
|
+
const source = JSON.parse(text(change.after.bytes));
|
|
96
|
+
const destination = JSON.parse(current === undefined ? "{}" : text(current));
|
|
97
|
+
if (!isJsonObject(source) || !isJsonObject(destination))
|
|
98
|
+
throw new Error(`Starter JSON merge requires objects: ${change.path}`);
|
|
99
|
+
const merged = candidates.reduce((result, candidate) => candidate.strategy === "tagged-merge"
|
|
100
|
+
? new TaggedMergeStrategy().mergeJson(source, result)
|
|
101
|
+
: mergeTemplateJson(source, result), destination);
|
|
102
|
+
if (JSON.stringify(merged) === JSON.stringify(destination))
|
|
103
|
+
return current;
|
|
104
|
+
return Buffer.from(`${JSON.stringify(merged, null, 2)}\n`);
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=starter-sync-content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-sync-content.js","sourceRoot":"","sources":["../../src/cli/starter-sync-content.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { StarterTemplate } from "../core/project-config-starter.js";
|
|
2
|
+
import { type CaptureCommand } from "./starter-provenance.js";
|
|
3
|
+
/** Exact regular-file or link bytes at one immutable starter revision. */
|
|
4
|
+
export interface StarterFile {
|
|
5
|
+
readonly bytes: Buffer;
|
|
6
|
+
readonly mode: string;
|
|
7
|
+
}
|
|
8
|
+
/** A path changed between the recorded baseline and the pinned tracked head. */
|
|
9
|
+
export interface StarterChange {
|
|
10
|
+
readonly path: string;
|
|
11
|
+
readonly before?: StarterFile;
|
|
12
|
+
readonly after?: StarterFile;
|
|
13
|
+
}
|
|
14
|
+
/** A complete, read-only diff suitable for one independently tracked starter. */
|
|
15
|
+
export interface StarterChanges {
|
|
16
|
+
readonly sha: string;
|
|
17
|
+
readonly changes: readonly StarterChange[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Keep Git paths relative without rejecting colon-scoped agent commands.
|
|
21
|
+
* @param value - Repository path or configured scope.
|
|
22
|
+
* @returns Validated path, with a scope's trailing slash removed.
|
|
23
|
+
*/
|
|
24
|
+
export declare function starterRelativePath(value: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Diff the actual recorded tree against one pinned head, never a merge base.
|
|
27
|
+
* @param template - Independently tracked starter and optional path scopes.
|
|
28
|
+
* @param capture - Bounded read-only GitHub metadata/content runner.
|
|
29
|
+
* @param accepts - Applicable ownership filter, applied before reading contents.
|
|
30
|
+
* @returns Complete scoped changes and the exact revision they describe.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readStarterChanges(template: StarterTemplate, capture?: CaptureCommand, accepts?: (name: string) => boolean): Promise<StarterChanges>;
|
|
33
|
+
//# sourceMappingURL=starter-sync-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-sync-source.d.ts","sourceRoot":"","sources":["../../src/cli/starter-sync-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAEzE,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,yBAAyB,CAAC;AAEjC,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,gFAAgF;AAChF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC;CAC9B;AAED,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;CAC5C;AAoBD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAWzD;AAyFD;;;;;;GAMG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE,cAA+B,EACxC,OAAO,GAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAoB,GAC9C,OAAO,CAAC,cAAc,CAAC,CA0CzB"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { isJsonObject } from "../sync/json-path.js";
|
|
2
|
+
import { captureCommand, readGitHubStarter, validateStarterRepo, } from "./starter-provenance.js";
|
|
3
|
+
/**
|
|
4
|
+
* Require a pinned Git object before putting it into a remote endpoint.
|
|
5
|
+
* @param value - Supplied or returned object identifier.
|
|
6
|
+
* @returns Validated identifier.
|
|
7
|
+
*/
|
|
8
|
+
function objectId(value) {
|
|
9
|
+
if (!/^(?:[a-f\d]{40}|[a-f\d]{64})$/i.test(value)) {
|
|
10
|
+
throw new Error("Starter revision is not a valid Git object identifier");
|
|
11
|
+
}
|
|
12
|
+
return value;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Keep Git paths relative without rejecting colon-scoped agent commands.
|
|
16
|
+
* @param value - Repository path or configured scope.
|
|
17
|
+
* @returns Validated path, with a scope's trailing slash removed.
|
|
18
|
+
*/
|
|
19
|
+
export function starterRelativePath(value) {
|
|
20
|
+
const normalized = value.replace(/\/$/, "");
|
|
21
|
+
if (!normalized ||
|
|
22
|
+
/[\\\u0000-\u001f]/u.test(normalized) ||
|
|
23
|
+
/^[a-z]:/iu.test(normalized) ||
|
|
24
|
+
normalized.split("/").some(part => !part || part === "." || part === "..")) {
|
|
25
|
+
throw new Error(`Invalid starter path: ${value}`);
|
|
26
|
+
}
|
|
27
|
+
return normalized;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Decode a file entry while leaving directory entries to the recursive tree.
|
|
31
|
+
* @param entry - Untrusted GitHub tree entry.
|
|
32
|
+
* @returns Zero or one validated file tuple.
|
|
33
|
+
*/
|
|
34
|
+
function treeEntry(entry) {
|
|
35
|
+
if (!isJsonObject(entry))
|
|
36
|
+
throw new Error("Starter returned an invalid tree entry");
|
|
37
|
+
if (entry.type === "tree")
|
|
38
|
+
return [];
|
|
39
|
+
if (typeof entry.path !== "string" ||
|
|
40
|
+
typeof entry.sha !== "string" ||
|
|
41
|
+
typeof entry.mode !== "string") {
|
|
42
|
+
throw new Error("Starter returned an invalid tree entry");
|
|
43
|
+
}
|
|
44
|
+
return [
|
|
45
|
+
[
|
|
46
|
+
starterRelativePath(entry.path),
|
|
47
|
+
{ sha: objectId(entry.sha), mode: entry.mode },
|
|
48
|
+
],
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Read a complete tree; an API limit must never look like deleted files.
|
|
53
|
+
* @param endpoint - Validated repository API endpoint.
|
|
54
|
+
* @param tree - Pinned tree object.
|
|
55
|
+
* @param capture - Read-only command runner.
|
|
56
|
+
* @returns Files indexed by their exact repository paths.
|
|
57
|
+
*/
|
|
58
|
+
async function readTree(endpoint, tree, capture) {
|
|
59
|
+
const data = JSON.parse(await capture("gh", [
|
|
60
|
+
"api",
|
|
61
|
+
`${endpoint}/git/trees/${objectId(tree)}?recursive=1`,
|
|
62
|
+
]));
|
|
63
|
+
if (!isJsonObject(data) || !Array.isArray(data.tree)) {
|
|
64
|
+
throw new Error("Starter returned an invalid Git tree");
|
|
65
|
+
}
|
|
66
|
+
if (data.truncated !== false) {
|
|
67
|
+
throw new Error("Starter Git tree is truncated or completeness is unknown");
|
|
68
|
+
}
|
|
69
|
+
const entries = data.tree.flatMap(treeEntry);
|
|
70
|
+
const files = new Map(entries);
|
|
71
|
+
if (files.size !== entries.length)
|
|
72
|
+
throw new Error("Starter returned duplicate tree paths");
|
|
73
|
+
return files;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Decode a pinned blob without trimming whitespace or executable metadata.
|
|
77
|
+
* @param endpoint - Validated repository API endpoint.
|
|
78
|
+
* @param file - File from the complete tree, or an absent side of the diff.
|
|
79
|
+
* @param capture - Read-only command runner.
|
|
80
|
+
* @returns Exact bytes and Git mode, or undefined when absent.
|
|
81
|
+
*/
|
|
82
|
+
async function readBlob(endpoint, file, capture) {
|
|
83
|
+
if (file === undefined)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (!["100644", "100755", "120000"].includes(file.mode)) {
|
|
86
|
+
throw new Error("Starter change has an unsupported Git file mode");
|
|
87
|
+
}
|
|
88
|
+
const data = JSON.parse(await capture("gh", ["api", `${endpoint}/git/blobs/${file.sha}`]));
|
|
89
|
+
if (!isJsonObject(data) ||
|
|
90
|
+
data.encoding !== "base64" ||
|
|
91
|
+
typeof data.content !== "string") {
|
|
92
|
+
throw new Error("Starter returned an invalid Git blob");
|
|
93
|
+
}
|
|
94
|
+
const bytes = Buffer.from(data.content, "base64");
|
|
95
|
+
if (data.size !== bytes.length)
|
|
96
|
+
throw new Error("Starter blob size does not match its contents");
|
|
97
|
+
return { bytes, mode: file.mode };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Diff the actual recorded tree against one pinned head, never a merge base.
|
|
101
|
+
* @param template - Independently tracked starter and optional path scopes.
|
|
102
|
+
* @param capture - Bounded read-only GitHub metadata/content runner.
|
|
103
|
+
* @param accepts - Applicable ownership filter, applied before reading contents.
|
|
104
|
+
* @returns Complete scoped changes and the exact revision they describe.
|
|
105
|
+
*/
|
|
106
|
+
export async function readStarterChanges(template, capture = captureCommand, accepts = () => true) {
|
|
107
|
+
const endpoint = `repos/${validateStarterRepo(template.repo)}`;
|
|
108
|
+
const baseline = objectId(template.lastSync.sha);
|
|
109
|
+
const scopes = template.paths?.map(starterRelativePath);
|
|
110
|
+
const head = await readGitHubStarter(template.repo, template.ref, capture);
|
|
111
|
+
const sha = head.template.lastSync.sha;
|
|
112
|
+
if (sha === baseline)
|
|
113
|
+
return { sha, changes: [] };
|
|
114
|
+
const baseTree = await capture("gh", [
|
|
115
|
+
"api",
|
|
116
|
+
`${endpoint}/git/commits/${baseline}`,
|
|
117
|
+
"--jq",
|
|
118
|
+
".tree.sha",
|
|
119
|
+
]);
|
|
120
|
+
const before = await readTree(endpoint, baseTree, capture);
|
|
121
|
+
const after = await readTree(endpoint, head.tree, capture);
|
|
122
|
+
const paths = [...new Set([...before.keys(), ...after.keys()])]
|
|
123
|
+
.sort((left, right) => left.localeCompare(right))
|
|
124
|
+
.filter(name => inScope(name, scopes))
|
|
125
|
+
.filter(accepts)
|
|
126
|
+
.filter(name => before.get(name)?.sha !== after.get(name)?.sha ||
|
|
127
|
+
before.get(name)?.mode !== after.get(name)?.mode);
|
|
128
|
+
// Read serially so a large starter never creates an unbounded request burst.
|
|
129
|
+
const changes = await paths.reduce(async (pending, name) => {
|
|
130
|
+
const completed = await pending;
|
|
131
|
+
const oldBytes = await readBlob(endpoint, before.get(name), capture);
|
|
132
|
+
const newBytes = await readBlob(endpoint, after.get(name), capture);
|
|
133
|
+
return [
|
|
134
|
+
...completed,
|
|
135
|
+
{
|
|
136
|
+
path: name,
|
|
137
|
+
...(oldBytes === undefined ? {} : { before: oldBytes }),
|
|
138
|
+
...(newBytes === undefined ? {} : { after: newBytes }),
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
}, Promise.resolve([]));
|
|
142
|
+
return { sha, changes };
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Match exact files or directory boundaries, rather than similar prefixes.
|
|
146
|
+
* @param name - Changed repository path.
|
|
147
|
+
* @param scopes - Validated optional configuration scopes.
|
|
148
|
+
* @returns Whether this path belongs to the requested diff.
|
|
149
|
+
*/
|
|
150
|
+
function inScope(name, scopes) {
|
|
151
|
+
return (scopes === undefined ||
|
|
152
|
+
scopes.some(scope => name === scope || name.startsWith(`${scope}/`)));
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=starter-sync-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-sync-source.js","sourceRoot":"","sources":["../../src/cli/starter-sync-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ProjectFileSnapshot } from "../health/read-only-fs.js";
|
|
2
|
+
/**
|
|
3
|
+
* Publish one guarded file update; an interrupted apply retains its old baseline.
|
|
4
|
+
* @param root - Canonical consumer root.
|
|
5
|
+
* @param name - Validated relative path.
|
|
6
|
+
* @param expected - Previously inspected bytes and mode.
|
|
7
|
+
* @param bytes - Replacement payload, or undefined to remove an owned file.
|
|
8
|
+
* @param mode - Final file permission bits.
|
|
9
|
+
*/
|
|
10
|
+
export declare function writeStarterFile(root: string, name: string, expected: ProjectFileSnapshot | undefined, bytes: Buffer | undefined, mode: number): Promise<void>;
|
|
11
|
+
//# sourceMappingURL=starter-sync-write.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-sync-write.d.ts","sourceRoot":"","sources":["../../src/cli/starter-sync-write.ts"],"names":[],"mappings":"AAWA,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,2BAA2B,CAAC;AAyHnC;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,mBAAmB,GAAG,SAAS,EACzC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC,CAcf"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { link, lstat, mkdir, mkdtemp, realpath, rename, rmdir, unlink, } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readProjectFile, resolveProjectPath, } from "../health/read-only-fs.js";
|
|
4
|
+
import { writeFileAtomically } from "../utils/atomic-file-write.js";
|
|
5
|
+
/**
|
|
6
|
+
* Verify every destination parent and create only missing consumer directories.
|
|
7
|
+
* @param root - Canonical consumer root.
|
|
8
|
+
* @param name - Validated relative file path.
|
|
9
|
+
* @param create - Whether missing parents may be created.
|
|
10
|
+
*/
|
|
11
|
+
async function parents(root, name, create) {
|
|
12
|
+
if ((await realpath(root)) !== root || (await lstat(root)).isSymbolicLink())
|
|
13
|
+
throw new Error("Starter consumer root changed");
|
|
14
|
+
const target = resolveProjectPath(root, name);
|
|
15
|
+
const parts = path
|
|
16
|
+
.relative(root, path.dirname(target))
|
|
17
|
+
.split(path.sep)
|
|
18
|
+
.filter(Boolean);
|
|
19
|
+
await parts.reduce(async (pending, part) => {
|
|
20
|
+
const parent = path.join(await pending, part);
|
|
21
|
+
if (create) {
|
|
22
|
+
try {
|
|
23
|
+
await mkdir(parent);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code !== "EEXIST")
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const stat = await lstat(parent);
|
|
31
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
32
|
+
throw new Error("Unsafe starter destination parent");
|
|
33
|
+
return parent;
|
|
34
|
+
}, Promise.resolve(root));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Refuse concurrent edits rather than overwriting a newer consumer snapshot.
|
|
38
|
+
* @param root - Canonical consumer root.
|
|
39
|
+
* @param name - Validated file path.
|
|
40
|
+
* @param expected - Snapshot used to decide this update.
|
|
41
|
+
*/
|
|
42
|
+
async function unchanged(root, name, expected) {
|
|
43
|
+
await parents(root, name, false);
|
|
44
|
+
const actual = await readProjectFile(root, name);
|
|
45
|
+
const sameBytes = actual === undefined
|
|
46
|
+
? expected === undefined
|
|
47
|
+
: expected !== undefined && actual.bytes.equals(expected.bytes);
|
|
48
|
+
if (!sameBytes || actual?.mode !== expected?.mode)
|
|
49
|
+
throw new Error(`Starter destination changed during sync: ${name}`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Restore a displaced entry without overwriting another concurrent replacement.
|
|
53
|
+
* @param root - Canonical consumer root.
|
|
54
|
+
* @param name - Original relative path.
|
|
55
|
+
* @param quarantine - Private path holding the displaced entry.
|
|
56
|
+
* @param failure - Original verification or removal error.
|
|
57
|
+
*/
|
|
58
|
+
async function restoreDisplaced(root, name, quarantine, failure) {
|
|
59
|
+
try {
|
|
60
|
+
await parents(root, name, false);
|
|
61
|
+
await link(quarantine, resolveProjectPath(root, name));
|
|
62
|
+
await unlink(quarantine);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
throw new Error(`Starter destination changed during sync: ${name}; displaced entry preserved at ${path.relative(root, quarantine)}`, { cause: new AggregateError([failure, error]) });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Pin a deletion by moving it privately and checking identity before unlinking.
|
|
70
|
+
* @param root - Canonical consumer root.
|
|
71
|
+
* @param name - Relative owned-file path.
|
|
72
|
+
* @param expected - Contents and permissions that justified deletion.
|
|
73
|
+
*/
|
|
74
|
+
async function removeUnchanged(root, name, expected) {
|
|
75
|
+
const target = resolveProjectPath(root, name);
|
|
76
|
+
const directory = await mkdtemp(path.join(path.dirname(target), ".lisa-starter-remove-"));
|
|
77
|
+
const quarantine = path.join(directory, "entry");
|
|
78
|
+
try {
|
|
79
|
+
const before = await lstat(target);
|
|
80
|
+
await unchanged(root, name, expected);
|
|
81
|
+
await rename(target, quarantine);
|
|
82
|
+
try {
|
|
83
|
+
const after = await lstat(quarantine);
|
|
84
|
+
if (before.dev !== after.dev || before.ino !== after.ino)
|
|
85
|
+
throw new Error(`Starter destination changed during sync: ${name}`);
|
|
86
|
+
await unchanged(root, path.relative(root, quarantine), expected);
|
|
87
|
+
await unchanged(root, name, undefined);
|
|
88
|
+
await unlink(quarantine);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
await restoreDisplaced(root, name, quarantine, error);
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await rmdir(directory).catch((error) => {
|
|
97
|
+
if (error.code !== "ENOTEMPTY")
|
|
98
|
+
throw error;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Publish one guarded file update; an interrupted apply retains its old baseline.
|
|
104
|
+
* @param root - Canonical consumer root.
|
|
105
|
+
* @param name - Validated relative path.
|
|
106
|
+
* @param expected - Previously inspected bytes and mode.
|
|
107
|
+
* @param bytes - Replacement payload, or undefined to remove an owned file.
|
|
108
|
+
* @param mode - Final file permission bits.
|
|
109
|
+
*/
|
|
110
|
+
export async function writeStarterFile(root, name, expected, bytes, mode) {
|
|
111
|
+
if (process.platform === "win32" && /[:<>"|?*]|[. ](?:\/|$)/u.test(name))
|
|
112
|
+
throw new Error("Starter path is not representable safely on Windows");
|
|
113
|
+
await parents(root, name, bytes !== undefined);
|
|
114
|
+
await unchanged(root, name, expected);
|
|
115
|
+
const target = resolveProjectPath(root, name);
|
|
116
|
+
if (bytes === undefined) {
|
|
117
|
+
if (expected !== undefined)
|
|
118
|
+
await removeUnchanged(root, name, expected);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
await writeFileAtomically(target, bytes, {
|
|
122
|
+
mode,
|
|
123
|
+
beforeRename: () => unchanged(root, name, expected),
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=starter-sync-write.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-sync-write.js","sourceRoot":"","sources":["../../src/cli/starter-sync-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { StarterTemplate } from "../core/project-config-starter.js";
|
|
2
|
+
import { type StarterChanges } from "./starter-sync-source.js";
|
|
3
|
+
/** Result for one independently tracked starter. */
|
|
4
|
+
export interface StarterSyncResult {
|
|
5
|
+
readonly repo: string;
|
|
6
|
+
readonly state: "updated" | "current" | "failed";
|
|
7
|
+
readonly changed: readonly string[];
|
|
8
|
+
readonly error?: string;
|
|
9
|
+
}
|
|
10
|
+
/** The caller supplies the isolated landing worktree; this is not a CLI default. */
|
|
11
|
+
export interface StarterSyncOptions {
|
|
12
|
+
readonly lisaRoot: string;
|
|
13
|
+
readonly projectRoot: string;
|
|
14
|
+
readonly readChanges?: (template: StarterTemplate, accepts: (name: string) => boolean) => Promise<StarterChanges>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Apply each starter independently using the existing template ownership rules.
|
|
18
|
+
* @param options - Lisa installation and caller-owned landing worktree.
|
|
19
|
+
* @returns Per-template outcomes, preserving failed baselines for retries.
|
|
20
|
+
*/
|
|
21
|
+
export declare function syncStarterTemplates(options: StarterSyncOptions): Promise<readonly StarterSyncResult[]>;
|
|
22
|
+
//# sourceMappingURL=starter-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"starter-sync.d.ts","sourceRoot":"","sources":["../../src/cli/starter-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAczE,OAAO,EAIL,KAAK,cAAc,EACpB,MAAM,0BAA0B,CAAC;AAUlC,oDAAoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,oFAAoF;AACpF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,CACrB,QAAQ,EAAE,eAAe,EACzB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,KAC/B,OAAO,CAAC,cAAc,CAAC,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAwBvC"}
|