project-tiny-context-harness 0.2.39
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/LICENSE +21 -0
- package/README.md +399 -0
- package/assets/README.md +455 -0
- package/assets/README.zh-CN.md +131 -0
- package/assets/agents/.gitkeep +1 -0
- package/assets/agents/AGENTS_CORE.md +58 -0
- package/assets/context_templates/architecture.md +31 -0
- package/assets/context_templates/area.md +31 -0
- package/assets/context_templates/context.toml +27 -0
- package/assets/context_templates/deployment.md +35 -0
- package/assets/context_templates/global.md +53 -0
- package/assets/context_templates/verification.md +31 -0
- package/assets/github/.gitkeep +1 -0
- package/assets/github/harness.yml +37 -0
- package/assets/make/.gitkeep +1 -0
- package/assets/make/sdlc-harness.mk +39 -0
- package/assets/skills/context_development_engineer/SKILL.md +86 -0
- package/assets/skills/context_full_project_export/SKILL.md +55 -0
- package/assets/skills/context_product_plan/SKILL.md +85 -0
- package/assets/skills/context_uiux_design/SKILL.md +110 -0
- package/assets/tools/validate_context.py +276 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +12 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +16 -0
- package/dist/commands/export-context.d.ts +1 -0
- package/dist/commands/export-context.js +149 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +33 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +108 -0
- package/dist/commands/package-source.d.ts +1 -0
- package/dist/commands/package-source.js +24 -0
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +14 -0
- package/dist/commands/upgrade.d.ts +1 -0
- package/dist/commands/upgrade.js +7 -0
- package/dist/commands/validate.d.ts +1 -0
- package/dist/commands/validate.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lib/config.d.ts +5 -0
- package/dist/lib/config.js +52 -0
- package/dist/lib/constants.d.ts +3 -0
- package/dist/lib/constants.js +3 -0
- package/dist/lib/context-export.d.ts +21 -0
- package/dist/lib/context-export.js +845 -0
- package/dist/lib/context-manifest.d.ts +3 -0
- package/dist/lib/context-manifest.js +103 -0
- package/dist/lib/context-templates.d.ts +5 -0
- package/dist/lib/context-templates.js +204 -0
- package/dist/lib/design-md.d.ts +2 -0
- package/dist/lib/design-md.js +132 -0
- package/dist/lib/doctor.d.ts +6 -0
- package/dist/lib/doctor.js +41 -0
- package/dist/lib/fs.d.ts +8 -0
- package/dist/lib/fs.js +56 -0
- package/dist/lib/harness-root.d.ts +9 -0
- package/dist/lib/harness-root.js +50 -0
- package/dist/lib/init.d.ts +5 -0
- package/dist/lib/init.js +65 -0
- package/dist/lib/managed-file.d.ts +19 -0
- package/dist/lib/managed-file.js +21 -0
- package/dist/lib/migrations.d.ts +11 -0
- package/dist/lib/migrations.js +180 -0
- package/dist/lib/package-json-config.d.ts +2 -0
- package/dist/lib/package-json-config.js +37 -0
- package/dist/lib/package-source.d.ts +8 -0
- package/dist/lib/package-source.js +124 -0
- package/dist/lib/paths.d.ts +5 -0
- package/dist/lib/paths.js +11 -0
- package/dist/lib/schema-guard.d.ts +3 -0
- package/dist/lib/schema-guard.js +28 -0
- package/dist/lib/sync-engine.d.ts +7 -0
- package/dist/lib/sync-engine.js +350 -0
- package/dist/lib/types.d.ts +21 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/upgrade.d.ts +1 -0
- package/dist/lib/upgrade.js +21 -0
- package/dist/lib/validators.d.ts +5 -0
- package/dist/lib/validators.js +459 -0
- package/dist/lib/yaml.d.ts +2 -0
- package/dist/lib/yaml.js +7 -0
- package/migrations/README.md +3 -0
- package/package.json +68 -0
- package/source-mappings.yaml +25 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import { readConfig } from "./config.js";
|
|
4
|
+
import { harnessPath, harnessRoot } from "./harness-root.js";
|
|
5
|
+
import { copyTree, listFiles, pathExists, readText, writeTextIfChanged } from "./fs.js";
|
|
6
|
+
import { AGENTS_BLOCK_MARKERS, GITHUB_WORKFLOW_BLOCK_END, GITHUB_WORKFLOW_BLOCK_START, MAKEFILE_BLOCK_END, MAKEFILE_BLOCK_MARKERS, MAKEFILE_BLOCK_START, MANAGED_BLOCK_END, MANAGED_BLOCK_START } from "./managed-file.js";
|
|
7
|
+
import { packageAssetPath } from "./paths.js";
|
|
8
|
+
import { assertSupportedSchema } from "./schema-guard.js";
|
|
9
|
+
export function emptySyncReport() {
|
|
10
|
+
return {
|
|
11
|
+
changed: [],
|
|
12
|
+
skipped: [],
|
|
13
|
+
blocked: []
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export async function runSync(projectRoot) {
|
|
17
|
+
await assertSupportedSchema(projectRoot, "sync");
|
|
18
|
+
const root = await harnessRoot(projectRoot);
|
|
19
|
+
const config = await readConfig(projectRoot);
|
|
20
|
+
const report = emptySyncReport();
|
|
21
|
+
await blockDeprecatedSkillOverrides(projectRoot, root, report);
|
|
22
|
+
if (report.blocked.length > 0) {
|
|
23
|
+
return report;
|
|
24
|
+
}
|
|
25
|
+
for (const managedFile of config.managed_files) {
|
|
26
|
+
await syncManagedFile(projectRoot, root, managedFile, report);
|
|
27
|
+
}
|
|
28
|
+
return report;
|
|
29
|
+
}
|
|
30
|
+
async function syncManagedFile(projectRoot, root, managedFile, report) {
|
|
31
|
+
const destination = path.join(projectRoot, managedFile.path);
|
|
32
|
+
if (managedFile.path === "AGENTS.md") {
|
|
33
|
+
await syncAgentsBlock(destination, root, report);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (managedFile.path === "Makefile") {
|
|
37
|
+
await syncMakefileInclude(destination, root, report);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (isSkillsManagedPath(managedFile.path, root)) {
|
|
41
|
+
await syncSkillsTree(packageAssetPath("skills"), path.join(projectRoot, root, "skills"), report);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const managedPath = normalizeManagedPath(managedFile.path);
|
|
45
|
+
if (managedPath === harnessPath(root, "pjsdlc_managed", "templates")) {
|
|
46
|
+
await syncTree(packageAssetPath("templates"), destination, report);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (managedPath === harnessPath(root, "pjsdlc_managed", "context_templates")) {
|
|
50
|
+
await syncTree(packageAssetPath("context_templates"), destination, report, { prune: true });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (managedPath === harnessPath(root, "pjsdlc_managed", "policies")) {
|
|
54
|
+
await syncTree(packageAssetPath("policies"), destination, report);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (managedPath === harnessPath(root, "pjsdlc_managed", "make", "sdlc-harness.mk")) {
|
|
58
|
+
await syncFile(packageAssetPath("make", "sdlc-harness.mk"), destination, report, "skip-if-missing");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (managedFile.path === "tools") {
|
|
62
|
+
await syncTree(packageAssetPath("tools"), destination, report);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (managedFile.path === ".github/workflows/harness.yml") {
|
|
66
|
+
await syncGithubWorkflow(packageAssetPath("github", "harness.yml"), destination, managedFile.path, report);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
report.skipped.push(managedFile.path);
|
|
70
|
+
}
|
|
71
|
+
function isSkillsManagedPath(managedPath, root) {
|
|
72
|
+
const normalized = normalizeManagedPath(managedPath);
|
|
73
|
+
return (normalized === harnessPath(root, "skills") ||
|
|
74
|
+
normalized === ".harness/agents/skills" ||
|
|
75
|
+
(normalized === ".agents/skills" && root !== ".agents"));
|
|
76
|
+
}
|
|
77
|
+
function normalizeManagedPath(managedPath) {
|
|
78
|
+
return managedPath.replace(/\\/g, "/");
|
|
79
|
+
}
|
|
80
|
+
async function syncAgentsBlock(destination, root, report) {
|
|
81
|
+
const corePath = packageAssetPath("agents", "AGENTS_CORE.md");
|
|
82
|
+
if (!(await pathExists(corePath))) {
|
|
83
|
+
report.skipped.push("AGENTS.md");
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const core = renderAgentsCore(await readText(corePath), root);
|
|
87
|
+
const block = `${MANAGED_BLOCK_START}\n${core.trim()}\n${MANAGED_BLOCK_END}`;
|
|
88
|
+
const existing = (await pathExists(destination)) ? await readText(destination) : "";
|
|
89
|
+
const next = mergeManagedBlock({
|
|
90
|
+
existing,
|
|
91
|
+
block,
|
|
92
|
+
markers: AGENTS_BLOCK_MARKERS,
|
|
93
|
+
pathLabel: "AGENTS.md",
|
|
94
|
+
insert: "append",
|
|
95
|
+
report
|
|
96
|
+
});
|
|
97
|
+
if (!next) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (await writeTextIfChanged(destination, next)) {
|
|
101
|
+
report.changed.push("AGENTS.md");
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
report.skipped.push("AGENTS.md");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function renderAgentsCore(content, root) {
|
|
108
|
+
return content.replaceAll(".agent", root).replaceAll(".codex", root);
|
|
109
|
+
}
|
|
110
|
+
async function syncMakefileInclude(destination, root, report) {
|
|
111
|
+
const existing = (await pathExists(destination)) ? await readText(destination) : "";
|
|
112
|
+
const resetDefaultGoal = shouldResetMakeDefaultGoal(existing);
|
|
113
|
+
const includePath = `${root.replace(/\\/g, "/")}/pjsdlc_managed/make/sdlc-harness.mk`;
|
|
114
|
+
const blockLines = [
|
|
115
|
+
MAKEFILE_BLOCK_START,
|
|
116
|
+
"# Included before project targets so project recipes win on name conflicts.",
|
|
117
|
+
`-include ${includePath}`,
|
|
118
|
+
MAKEFILE_BLOCK_END
|
|
119
|
+
];
|
|
120
|
+
if (resetDefaultGoal) {
|
|
121
|
+
blockLines.splice(3, 0, ".DEFAULT_GOAL :=");
|
|
122
|
+
}
|
|
123
|
+
const block = blockLines.join("\n");
|
|
124
|
+
const next = mergeManagedBlock({
|
|
125
|
+
existing,
|
|
126
|
+
block,
|
|
127
|
+
markers: MAKEFILE_BLOCK_MARKERS,
|
|
128
|
+
pathLabel: "Makefile",
|
|
129
|
+
insert: "prepend",
|
|
130
|
+
report
|
|
131
|
+
});
|
|
132
|
+
if (!next) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (await writeTextIfChanged(destination, next)) {
|
|
136
|
+
report.changed.push("Makefile");
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
report.skipped.push("Makefile");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function shouldResetMakeDefaultGoal(existing) {
|
|
143
|
+
if (!existing.trim()) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
const block = findManagedBlock(existing, MAKEFILE_BLOCK_MARKERS);
|
|
147
|
+
if (block.status === "missing") {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
if (block.status === "invalid") {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const before = existing.slice(0, block.startIndex);
|
|
154
|
+
const after = existing.slice(block.endIndex + block.markers.end.length);
|
|
155
|
+
return !before.trim() && Boolean(after.trim());
|
|
156
|
+
}
|
|
157
|
+
function mergeManagedBlock(options) {
|
|
158
|
+
const { existing, block, markers, pathLabel, insert, report } = options;
|
|
159
|
+
const found = findManagedBlock(existing, markers);
|
|
160
|
+
if (found.status === "invalid") {
|
|
161
|
+
report.blocked.push(`${pathLabel}: ${found.reason}`);
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
if (found.status === "found") {
|
|
165
|
+
const before = existing.slice(0, found.startIndex);
|
|
166
|
+
const after = existing.slice(found.endIndex + found.markers.end.length);
|
|
167
|
+
return `${before}${block}${after}`;
|
|
168
|
+
}
|
|
169
|
+
if (!existing.trim()) {
|
|
170
|
+
return `${block}\n`;
|
|
171
|
+
}
|
|
172
|
+
if (insert === "prepend") {
|
|
173
|
+
return `${block}\n\n${existing}`;
|
|
174
|
+
}
|
|
175
|
+
return `${existing.trimEnd()}\n\n${block}\n`;
|
|
176
|
+
}
|
|
177
|
+
function findManagedBlock(existing, markersList) {
|
|
178
|
+
const matches = [];
|
|
179
|
+
for (const markers of markersList) {
|
|
180
|
+
const startIndex = existing.indexOf(markers.start);
|
|
181
|
+
const endIndex = existing.indexOf(markers.end);
|
|
182
|
+
const hasStart = startIndex >= 0;
|
|
183
|
+
const hasEnd = endIndex >= 0;
|
|
184
|
+
if (!hasStart && !hasEnd) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
if (hasStart !== hasEnd || endIndex < startIndex) {
|
|
188
|
+
return { status: "invalid", reason: "incomplete managed block markers" };
|
|
189
|
+
}
|
|
190
|
+
if (existing.indexOf(markers.start, startIndex + markers.start.length) >= 0 ||
|
|
191
|
+
existing.indexOf(markers.end, endIndex + markers.end.length) >= 0) {
|
|
192
|
+
return { status: "invalid", reason: "duplicate managed block markers" };
|
|
193
|
+
}
|
|
194
|
+
matches.push({ markers, startIndex, endIndex });
|
|
195
|
+
}
|
|
196
|
+
if (matches.length > 1) {
|
|
197
|
+
return { status: "invalid", reason: "conflicting managed block marker namespaces" };
|
|
198
|
+
}
|
|
199
|
+
return matches[0] ? { status: "found", ...matches[0] } : { status: "missing" };
|
|
200
|
+
}
|
|
201
|
+
async function syncTree(source, destination, report, options = {}) {
|
|
202
|
+
if (!(await pathExists(source))) {
|
|
203
|
+
report.skipped.push(path.basename(destination));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
const files = await listFiles(source);
|
|
207
|
+
const realFiles = files.filter((file) => !file.endsWith(".gitkeep"));
|
|
208
|
+
if (realFiles.length === 0) {
|
|
209
|
+
report.skipped.push(path.basename(destination));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const changed = await copyTree(source, destination, { skipGitkeep: true });
|
|
213
|
+
if (options.prune) {
|
|
214
|
+
changed.push(...(await removeStaleManagedFiles(source, destination)));
|
|
215
|
+
}
|
|
216
|
+
report.changed.push(...changed);
|
|
217
|
+
}
|
|
218
|
+
async function removeStaleManagedFiles(source, destination) {
|
|
219
|
+
if (!(await pathExists(destination))) {
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
const sourceFiles = new Set((await listFiles(source))
|
|
223
|
+
.filter((file) => !file.endsWith(".gitkeep"))
|
|
224
|
+
.map((file) => path.relative(source, file).split(path.sep).join("/")));
|
|
225
|
+
const removed = [];
|
|
226
|
+
for (const destinationFile of await listFiles(destination)) {
|
|
227
|
+
const relative = path.relative(destination, destinationFile).split(path.sep).join("/");
|
|
228
|
+
if (sourceFiles.has(relative)) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
await fs.rm(destinationFile, { force: true });
|
|
232
|
+
removed.push(destinationFile);
|
|
233
|
+
}
|
|
234
|
+
return removed;
|
|
235
|
+
}
|
|
236
|
+
async function syncSkillsTree(source, destination, report) {
|
|
237
|
+
if (!(await pathExists(source))) {
|
|
238
|
+
report.skipped.push(path.basename(destination));
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const files = await listFiles(source);
|
|
242
|
+
const realFiles = files.filter((file) => !file.endsWith(".gitkeep"));
|
|
243
|
+
if (realFiles.length === 0) {
|
|
244
|
+
report.skipped.push(path.basename(destination));
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
for (const file of realFiles) {
|
|
248
|
+
const relative = path.relative(source, file);
|
|
249
|
+
const destinationFile = path.join(destination, relative);
|
|
250
|
+
if (await writeTextIfChanged(destinationFile, await readText(file))) {
|
|
251
|
+
report.changed.push(destinationFile);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
report.skipped.push(destinationFile);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function blockDeprecatedSkillOverrides(projectRoot, root, report) {
|
|
259
|
+
const overrideRoot = skillOverrideRoot(projectRoot, root);
|
|
260
|
+
if (!(await pathExists(overrideRoot))) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const deprecatedFiles = (await listFiles(overrideRoot))
|
|
264
|
+
.filter((file) => path.basename(file) !== ".gitkeep")
|
|
265
|
+
.map((file) => path.relative(overrideRoot, file).split(path.sep).join("/"))
|
|
266
|
+
.sort();
|
|
267
|
+
if (deprecatedFiles.length === 0) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const relativeRoot = path.join(root, "pjsdlc_managed", "override_skills").split(path.sep).join("/");
|
|
271
|
+
report.blocked.push(`${relativeRoot}: Skill overrides are no longer supported. Move these rules into a separate project-local Skill such as ${root.replace(/\\/g, "/")}/skills/product_plan/SKILL.md, ${root.replace(/\\/g, "/")}/skills/uiux_design/SKILL.md or ${root.replace(/\\/g, "/")}/skills/development_engineer/SKILL.md. Deprecated files: ${deprecatedFiles.join(", ")}`);
|
|
272
|
+
}
|
|
273
|
+
function skillOverrideRoot(projectRoot, root) {
|
|
274
|
+
return path.join(projectRoot, root, "pjsdlc_managed", "override_skills");
|
|
275
|
+
}
|
|
276
|
+
async function syncFile(source, destination, report, missingMode) {
|
|
277
|
+
if (!(await pathExists(source))) {
|
|
278
|
+
if (missingMode === "block-if-missing") {
|
|
279
|
+
report.blocked.push(source);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
report.skipped.push(destination);
|
|
283
|
+
}
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (await writeTextIfChanged(destination, await readText(source))) {
|
|
287
|
+
report.changed.push(destination);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
report.skipped.push(destination);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function syncGithubWorkflow(source, destination, relativePath, report) {
|
|
294
|
+
if (!(await pathExists(source))) {
|
|
295
|
+
report.skipped.push(relativePath);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const sourceContent = await readText(source);
|
|
299
|
+
if (!(await pathExists(destination))) {
|
|
300
|
+
if (await writeTextIfChanged(destination, sourceContent)) {
|
|
301
|
+
report.changed.push(relativePath);
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
report.skipped.push(relativePath);
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const existing = await readText(destination);
|
|
309
|
+
const markerState = workflowMarkerState(existing);
|
|
310
|
+
if (markerState === "invalid") {
|
|
311
|
+
report.blocked.push(`${relativePath}: incomplete managed workflow markers`);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (markerState === "managed" || normalizeWorkflow(existing) === normalizeWorkflow(stripWorkflowMarkers(sourceContent))) {
|
|
315
|
+
if (await writeTextIfChanged(destination, sourceContent)) {
|
|
316
|
+
report.changed.push(relativePath);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
report.skipped.push(relativePath);
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
report.skipped.push(`${relativePath}: customized`);
|
|
324
|
+
}
|
|
325
|
+
function workflowMarkerState(content) {
|
|
326
|
+
const startIndex = content.indexOf(GITHUB_WORKFLOW_BLOCK_START);
|
|
327
|
+
const endIndex = content.indexOf(GITHUB_WORKFLOW_BLOCK_END);
|
|
328
|
+
const hasStart = startIndex >= 0;
|
|
329
|
+
const hasEnd = endIndex >= 0;
|
|
330
|
+
if (!hasStart && !hasEnd) {
|
|
331
|
+
return "missing";
|
|
332
|
+
}
|
|
333
|
+
if (hasStart !== hasEnd || endIndex < startIndex) {
|
|
334
|
+
return "invalid";
|
|
335
|
+
}
|
|
336
|
+
if (content.indexOf(GITHUB_WORKFLOW_BLOCK_START, startIndex + GITHUB_WORKFLOW_BLOCK_START.length) >= 0 ||
|
|
337
|
+
content.indexOf(GITHUB_WORKFLOW_BLOCK_END, endIndex + GITHUB_WORKFLOW_BLOCK_END.length) >= 0) {
|
|
338
|
+
return "invalid";
|
|
339
|
+
}
|
|
340
|
+
return "managed";
|
|
341
|
+
}
|
|
342
|
+
function stripWorkflowMarkers(content) {
|
|
343
|
+
return content
|
|
344
|
+
.split(/\r?\n/)
|
|
345
|
+
.filter((line) => line.trim() !== GITHUB_WORKFLOW_BLOCK_START && line.trim() !== GITHUB_WORKFLOW_BLOCK_END)
|
|
346
|
+
.join("\n");
|
|
347
|
+
}
|
|
348
|
+
function normalizeWorkflow(content) {
|
|
349
|
+
return content.replace(/\r\n/g, "\n").trim();
|
|
350
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface HarnessConfig {
|
|
2
|
+
core: {
|
|
3
|
+
package: string;
|
|
4
|
+
schema_version: string;
|
|
5
|
+
};
|
|
6
|
+
managed_files: ManagedFile[];
|
|
7
|
+
never_overwrite: string[];
|
|
8
|
+
}
|
|
9
|
+
export interface ManagedFile {
|
|
10
|
+
path: string;
|
|
11
|
+
strategy: "merge-block" | "generated" | "generated-compat" | "managed" | "merge-with-local" | "create-if-missing";
|
|
12
|
+
}
|
|
13
|
+
export interface SourceMapping {
|
|
14
|
+
source: string;
|
|
15
|
+
target: string;
|
|
16
|
+
exclude?: string[];
|
|
17
|
+
mode: "extract-managed-block" | "copy-tree" | "copy-file" | "extract-harness-targets";
|
|
18
|
+
}
|
|
19
|
+
export interface SourceMappingsFile {
|
|
20
|
+
source_mappings: SourceMapping[];
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runUpgrade(projectRoot: string): Promise<string[]>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { runDoctor } from "./doctor.js";
|
|
2
|
+
import { runMigrations } from "./migrations.js";
|
|
3
|
+
import { assertSupportedSchema } from "./schema-guard.js";
|
|
4
|
+
import { runSync } from "./sync-engine.js";
|
|
5
|
+
export async function runUpgrade(projectRoot) {
|
|
6
|
+
const lines = [];
|
|
7
|
+
await assertSupportedSchema(projectRoot, "upgrade");
|
|
8
|
+
const migrationReport = await runMigrations(projectRoot);
|
|
9
|
+
lines.push(`migrations changed=${migrationReport.changed.length} skipped=${migrationReport.skipped.length}`);
|
|
10
|
+
const syncReport = await runSync(projectRoot);
|
|
11
|
+
lines.push(`sync changed=${syncReport.changed.length} skipped=${syncReport.skipped.length} blocked=${syncReport.blocked.length}`);
|
|
12
|
+
for (const skipped of syncReport.skipped.filter((line) => line.includes("customized"))) {
|
|
13
|
+
lines.push(`sync skipped: ${skipped}`);
|
|
14
|
+
}
|
|
15
|
+
const doctor = await runDoctor(projectRoot);
|
|
16
|
+
lines.push(`doctor warnings=${doctor.warnings.length} errors=${doctor.errors.length}`);
|
|
17
|
+
if (syncReport.blocked.length > 0 || doctor.errors.length > 0) {
|
|
18
|
+
throw new Error("upgrade completed with blockers");
|
|
19
|
+
}
|
|
20
|
+
return lines;
|
|
21
|
+
}
|