@lemoncode/lemony 0.1.1-alpha.0 → 0.1.1-alpha.2
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/NOTICE +3 -1
- package/README.md +0 -1
- package/catalog/VERSION +1 -1
- package/catalog/agents/implementer.md +1 -1
- package/catalog/agents/orchestrator.md +76 -49
- package/catalog/agents/spec-author.md +4 -1
- package/catalog/agents/ui-designer.md +69 -63
- package/catalog/commands/resume.md +3 -3
- package/catalog/harness.config.schema.json +1 -28
- package/catalog/skills/design-critique/SKILL.md +1 -1
- package/catalog/skills/grill-ui/SKILL.md +112 -50
- package/catalog/skills/grill-ui/ui-handoff-format.md +9 -8
- package/catalog/skills/grill-with-docs/SKILL.md +1 -0
- package/catalog/skills/tdd/SKILL.md +8 -0
- package/catalog/skills/write-adr/SKILL.md +8 -0
- package/catalog/templates/claude-code/agents.md.tpl +15 -12
- package/catalog/templates/claude-code/harness.config.yml.tpl +0 -12
- package/dist/cli.mjs +55 -52
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { delimiter, dirname, extname, join, relative, resolve, sep } from "node:
|
|
|
5
5
|
import { argv, cwd, env, exit, stderr, stdin, stdout } from "node:process";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
7
|
import { promisify } from "node:util";
|
|
8
|
-
import { parse, parseDocument } from "yaml";
|
|
8
|
+
import { isMap, parse, parseDocument } from "yaml";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { createHash, randomBytes } from "node:crypto";
|
|
11
11
|
import { constants } from "node:fs";
|
|
@@ -17,27 +17,20 @@ const appendEvent = async (eventsPath, event) => {
|
|
|
17
17
|
//#endregion
|
|
18
18
|
//#region src/config/config.constant.ts
|
|
19
19
|
const HARNESS_CONFIG_FILENAME = "harness.config.yml";
|
|
20
|
-
const DEPRECATED_CONFIG_KEYS = ["profile"];
|
|
20
|
+
const DEPRECATED_CONFIG_KEYS = ["profile", "agents"];
|
|
21
|
+
const DEPRECATED_PATHS_KEYS = [
|
|
22
|
+
"state",
|
|
23
|
+
"skills",
|
|
24
|
+
"agents"
|
|
25
|
+
];
|
|
21
26
|
const HARNESS_CONFIG_SCHEMA_FILENAME = "harness.config.schema.json";
|
|
22
27
|
const TASK_STORAGE_REPO_PLACEHOLDER = "OWNER/REPO";
|
|
23
28
|
const TARGETS = ["claude-code"];
|
|
24
29
|
const TASK_STORAGE_TYPES = ["github"];
|
|
25
30
|
const TASK_STORAGE_REPO_PATTERN = /^[^\s/]+\/[^\s/]+$/;
|
|
26
|
-
const AGENTS = [
|
|
27
|
-
"orchestrator",
|
|
28
|
-
"spec-author",
|
|
29
|
-
"implementer",
|
|
30
|
-
"reviewer",
|
|
31
|
-
"architect",
|
|
32
|
-
"ui-designer"
|
|
33
|
-
];
|
|
34
|
-
const REQUIRED_AGENT = "orchestrator";
|
|
35
31
|
const VENDOR_VERSION_REGEX = /^\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$/;
|
|
36
32
|
const VENDOR_VERSION_EXAMPLE = "0.1.0-alpha.0";
|
|
37
33
|
const PATHS_DEFAULTS = {
|
|
38
|
-
state: ".claude/state",
|
|
39
|
-
skills: ".claude/skills",
|
|
40
|
-
agents: ".claude/agents",
|
|
41
34
|
playbooks: "docs/playbooks",
|
|
42
35
|
playbooks_global: "~/.claude/playbooks"
|
|
43
36
|
};
|
|
@@ -106,9 +99,6 @@ const levenshtein = (a, b) => {
|
|
|
106
99
|
//#endregion
|
|
107
100
|
//#region src/config/config.schema.ts
|
|
108
101
|
const pathsSchema = z.object({
|
|
109
|
-
state: z.string().default(PATHS_DEFAULTS.state),
|
|
110
|
-
skills: z.string().default(PATHS_DEFAULTS.skills),
|
|
111
|
-
agents: z.string().default(PATHS_DEFAULTS.agents),
|
|
112
102
|
playbooks: z.string().default(PATHS_DEFAULTS.playbooks),
|
|
113
103
|
playbooks_global: z.string().default(PATHS_DEFAULTS.playbooks_global)
|
|
114
104
|
}).strict().prefault({});
|
|
@@ -123,7 +113,6 @@ const harnessConfigSchema = z.object({
|
|
|
123
113
|
vendor_version: z.string().regex(VENDOR_VERSION_REGEX),
|
|
124
114
|
target: z.enum(TARGETS),
|
|
125
115
|
task_storage: taskStorageSchema,
|
|
126
|
-
agents: z.array(z.enum(AGENTS)).refine((agents) => agents.includes(REQUIRED_AGENT), { error: `agents must include "${REQUIRED_AGENT}" (the orchestrator is the hat).` }),
|
|
127
116
|
paths: pathsSchema,
|
|
128
117
|
rollback: rollbackSchema,
|
|
129
118
|
telemetry: telemetrySchema,
|
|
@@ -149,6 +138,12 @@ const readHarnessConfig = async (repoRoot) => {
|
|
|
149
138
|
if (!parsed || typeof parsed !== "object") throw new Error(`${HARNESS_CONFIG_FILENAME} is empty or malformed.`);
|
|
150
139
|
const candidate = { ...parsed };
|
|
151
140
|
for (const key of DEPRECATED_CONFIG_KEYS) delete candidate[key];
|
|
141
|
+
const paths = candidate.paths;
|
|
142
|
+
if (paths && typeof paths === "object" && !Array.isArray(paths)) {
|
|
143
|
+
const pathsCandidate = { ...paths };
|
|
144
|
+
for (const key of DEPRECATED_PATHS_KEYS) delete pathsCandidate[key];
|
|
145
|
+
candidate.paths = pathsCandidate;
|
|
146
|
+
}
|
|
152
147
|
const result = harnessConfigSchema.safeParse(candidate);
|
|
153
148
|
if (!result.success) throw new Error(`${HARNESS_CONFIG_FILENAME} is invalid:\n${formatConfigError(harnessConfigSchema, result.error)}`);
|
|
154
149
|
return result.data;
|
|
@@ -204,6 +199,9 @@ const setConfigValues = (rawYaml, updates) => {
|
|
|
204
199
|
const doc = parseDocument(rawYaml);
|
|
205
200
|
for (const [key, value] of Object.entries(updates)) if (doc.has(key)) doc.set(key, value);
|
|
206
201
|
for (const key of DEPRECATED_CONFIG_KEYS) if (doc.has(key)) doc.delete(key);
|
|
202
|
+
for (const key of DEPRECATED_PATHS_KEYS) if (doc.hasIn(["paths", key])) doc.deleteIn(["paths", key]);
|
|
203
|
+
const pathsNode = doc.get("paths", true);
|
|
204
|
+
if (isMap(pathsNode) && pathsNode.items.length === 0) doc.delete("paths");
|
|
207
205
|
return doc.toString();
|
|
208
206
|
};
|
|
209
207
|
const writeConfigValues = async (repoRoot, updates) => {
|
|
@@ -226,6 +224,19 @@ const pointerFrontmatterSchema = z.object({
|
|
|
226
224
|
});
|
|
227
225
|
Object.keys(pointerFrontmatterSchema.shape);
|
|
228
226
|
//#endregion
|
|
227
|
+
//#region src/paths/claude-paths.constant.ts
|
|
228
|
+
const CLAUDE_DIR = ".claude";
|
|
229
|
+
const STATE_DIR = join(CLAUDE_DIR, "state");
|
|
230
|
+
const SKILLS_DIR = join(CLAUDE_DIR, "skills");
|
|
231
|
+
const AGENTS_DIR = join(CLAUDE_DIR, "agents");
|
|
232
|
+
const HOOKS_DIR = join(CLAUDE_DIR, "hooks");
|
|
233
|
+
const COMMANDS_DIR = join(CLAUDE_DIR, "commands");
|
|
234
|
+
const SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
|
|
235
|
+
const HARNESS_DIR = join(CLAUDE_DIR, ".harness");
|
|
236
|
+
const BASELINE_ROOT_REL = join(HARNESS_DIR, "baseline");
|
|
237
|
+
const SNAPSHOTS_ROOT_REL = join(HARNESS_DIR, "snapshots");
|
|
238
|
+
const EVENTS_RELPATH = join(STATE_DIR, "events.jsonl");
|
|
239
|
+
//#endregion
|
|
229
240
|
//#region src/events/envelope.ts
|
|
230
241
|
const buildEnvelope = (input) => {
|
|
231
242
|
const ts = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
@@ -399,7 +410,6 @@ const NUMERIC_FIELDS = /* @__PURE__ */ new Set([
|
|
|
399
410
|
"review_iterations"
|
|
400
411
|
]);
|
|
401
412
|
const BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["auto_close"]);
|
|
402
|
-
const EVENTS_RELPATH$1 = join(".claude", "state", "events.jsonl");
|
|
403
413
|
const isEventType = (value) => EVENT_TYPES.includes(value);
|
|
404
414
|
const coerceField = (key, raw) => {
|
|
405
415
|
if (NUMERIC_FIELDS.has(key)) {
|
|
@@ -454,7 +464,7 @@ const runEmit = async (options) => {
|
|
|
454
464
|
const issues = parsed.error.issues.map((issue) => ` - ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
|
|
455
465
|
throw new Error(`Event validation failed for type "${rawType}":\n${issues}`);
|
|
456
466
|
}
|
|
457
|
-
const eventsPath = join(repoRoot, EVENTS_RELPATH
|
|
467
|
+
const eventsPath = join(repoRoot, EVENTS_RELPATH);
|
|
458
468
|
await appendEvent(eventsPath, parsed.data);
|
|
459
469
|
return {
|
|
460
470
|
event: parsed.data,
|
|
@@ -2221,9 +2231,8 @@ const sanitizeLine = (raw, tier) => {
|
|
|
2221
2231
|
};
|
|
2222
2232
|
//#endregion
|
|
2223
2233
|
//#region src/telemetry/telemetry.constant.ts
|
|
2224
|
-
const
|
|
2225
|
-
const
|
|
2226
|
-
const REJECTED_RELPATH = join(".claude", "state", "telemetry-rejected.jsonl");
|
|
2234
|
+
const CURSOR_RELPATH = join(STATE_DIR, "telemetry-cursor.json");
|
|
2235
|
+
const REJECTED_RELPATH = join(STATE_DIR, "telemetry-rejected.jsonl");
|
|
2227
2236
|
const PAYLOAD_CAP_BYTES = 1e6;
|
|
2228
2237
|
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
2229
2238
|
const PRUNE_THRESHOLD_BYTES = 5e6;
|
|
@@ -2285,7 +2294,7 @@ const chunkTail = (rawTail, tier, capBytes = PAYLOAD_CAP_BYTES) => {
|
|
|
2285
2294
|
};
|
|
2286
2295
|
//#endregion
|
|
2287
2296
|
//#region src/telemetry/consent.constant.ts
|
|
2288
|
-
const CONSENT_RELPATH = join(
|
|
2297
|
+
const CONSENT_RELPATH = join(STATE_DIR, "telemetry-consent.json");
|
|
2289
2298
|
const ENV_DISABLE_VALUES = [
|
|
2290
2299
|
"1",
|
|
2291
2300
|
"true",
|
|
@@ -2681,7 +2690,7 @@ const formatTelemetryShow = (report) => {
|
|
|
2681
2690
|
};
|
|
2682
2691
|
//#endregion
|
|
2683
2692
|
//#region src/telemetry/telemetry-notice.constant.ts
|
|
2684
|
-
const NOTICE_SENTINEL_RELPATH = join(
|
|
2693
|
+
const NOTICE_SENTINEL_RELPATH = join(STATE_DIR, ".telemetry-notice-shown");
|
|
2685
2694
|
const NOTICE_MESSAGE = "Anonymous telemetry is ON — run `lemony telemetry disable` to opt out. See PRIVACY.md.";
|
|
2686
2695
|
//#endregion
|
|
2687
2696
|
//#region src/telemetry/telemetry-notice.ts
|
|
@@ -3071,7 +3080,7 @@ const checkLabels = async (deps, config) => {
|
|
|
3071
3080
|
};
|
|
3072
3081
|
const checkHooks = async (repoRoot) => {
|
|
3073
3082
|
const name = "hooks";
|
|
3074
|
-
const settingsPath = join(repoRoot,
|
|
3083
|
+
const settingsPath = join(repoRoot, SETTINGS_FILE);
|
|
3075
3084
|
let raw;
|
|
3076
3085
|
try {
|
|
3077
3086
|
raw = await readFile(settingsPath, "utf8");
|
|
@@ -3327,7 +3336,7 @@ const readPointer = async (repoRoot, readGitUserEmail) => {
|
|
|
3327
3336
|
if (!email) return empty;
|
|
3328
3337
|
const slug = email.split("@")[0] ?? "";
|
|
3329
3338
|
if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..")) return empty;
|
|
3330
|
-
const pointerPath = join(repoRoot,
|
|
3339
|
+
const pointerPath = join(repoRoot, STATE_DIR, `current-${slug}.md`);
|
|
3331
3340
|
let raw;
|
|
3332
3341
|
try {
|
|
3333
3342
|
raw = await readFile(pointerPath, "utf8");
|
|
@@ -3460,7 +3469,6 @@ const renderTemplate = (template, vars) => template.replace(PLACEHOLDER, (_match
|
|
|
3460
3469
|
});
|
|
3461
3470
|
//#endregion
|
|
3462
3471
|
//#region src/baseline/baseline.ts
|
|
3463
|
-
const BASELINE_ROOT_REL = join(".claude", ".harness", "baseline");
|
|
3464
3472
|
const STAGING_DIR$1 = ".staging";
|
|
3465
3473
|
const isENOENT$1 = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
3466
3474
|
const baselineRootDir = (repoRoot) => join(repoRoot, BASELINE_ROOT_REL);
|
|
@@ -3525,7 +3533,6 @@ const findBaselineVersion = async (repoRoot) => {
|
|
|
3525
3533
|
};
|
|
3526
3534
|
//#endregion
|
|
3527
3535
|
//#region src/snapshot/snapshot.ts
|
|
3528
|
-
const SNAPSHOTS_ROOT_REL = join(".claude", ".harness", "snapshots");
|
|
3529
3536
|
const STAGING_DIR = ".staging";
|
|
3530
3537
|
const WORKING_SUBDIR = "working";
|
|
3531
3538
|
const BASELINE_SUBDIR = "baseline";
|
|
@@ -3785,12 +3792,11 @@ const lcsMatches = (base, other) => {
|
|
|
3785
3792
|
};
|
|
3786
3793
|
//#endregion
|
|
3787
3794
|
//#region src/update/engine.ts
|
|
3788
|
-
const SKILLS_PREFIX = join(".claude", "skills");
|
|
3789
3795
|
const cohesionKey = (relPath) => {
|
|
3790
|
-
const prefix =
|
|
3796
|
+
const prefix = SKILLS_DIR + sep;
|
|
3791
3797
|
if (!relPath.startsWith(prefix)) return relPath;
|
|
3792
3798
|
const name = relPath.slice(prefix.length).split(sep)[0];
|
|
3793
|
-
return name ? join(
|
|
3799
|
+
return name ? join(SKILLS_DIR, name) : relPath;
|
|
3794
3800
|
};
|
|
3795
3801
|
const runEngine = async (inputs) => {
|
|
3796
3802
|
const { baseline, readClient, onConflict } = inputs;
|
|
@@ -3935,23 +3941,23 @@ const materializeVendorFiles = async (ctx) => {
|
|
|
3935
3941
|
const templateRoot = join(vendorRoot, "templates", target);
|
|
3936
3942
|
const vendorVersion = vars.vendor_version ?? "";
|
|
3937
3943
|
const entryProtocol = await renderFile(join(templateRoot, "agents.md.tpl"), "agents.md", vars);
|
|
3938
|
-
const agentFiles = await Promise.all(CORE_AGENTS.map(async (role) => withAssetFrontmatter(await renderFile(join(vendorRoot, "agents", `${role}.md`), join(
|
|
3944
|
+
const agentFiles = await Promise.all(CORE_AGENTS.map(async (role) => withAssetFrontmatter(await renderFile(join(vendorRoot, "agents", `${role}.md`), join(AGENTS_DIR, `${role}.md`), {
|
|
3939
3945
|
SKILLS: renderRoleSkills(skills, role),
|
|
3940
3946
|
vendor_version: vendorVersion
|
|
3941
3947
|
}), vendorVersion)));
|
|
3942
|
-
const fitAssessment = await copyFileEntry(join(vendorRoot, "agents", "fit-assessment.md"), join(
|
|
3948
|
+
const fitAssessment = await copyFileEntry(join(vendorRoot, "agents", "fit-assessment.md"), join(AGENTS_DIR, "fit-assessment.md"));
|
|
3943
3949
|
const skillFiles = (await Promise.all(skills.map(async (skill) => {
|
|
3944
3950
|
const skillDir = join(vendorRoot, "skills", skill.name);
|
|
3945
3951
|
const rels = await listFiles(skillDir);
|
|
3946
|
-
return Promise.all(rels.map(async (rel) => withAssetFrontmatter(await copyFileEntry(join(skillDir, rel), join(
|
|
3952
|
+
return Promise.all(rels.map(async (rel) => withAssetFrontmatter(await copyFileEntry(join(skillDir, rel), join(SKILLS_DIR, skill.name, rel)), vendorVersion)));
|
|
3947
3953
|
}))).flat();
|
|
3948
3954
|
const playbooksReadme = await renderFile(join(templateRoot, "docs", "playbooks", "README.md.tpl"), join("docs", "playbooks", "README.md"), vars);
|
|
3949
3955
|
const hooksSrc = join(vendorRoot, "hooks");
|
|
3950
|
-
const hookFiles = await Promise.all((await listFiles(hooksSrc)).filter((rel) => rel.endsWith(".sh") && !rel.includes(sep)).map((rel) => copyFileEntry(join(hooksSrc, rel), join(
|
|
3956
|
+
const hookFiles = await Promise.all((await listFiles(hooksSrc)).filter((rel) => rel.endsWith(".sh") && !rel.includes(sep)).map((rel) => copyFileEntry(join(hooksSrc, rel), join(HOOKS_DIR, rel), true)));
|
|
3951
3957
|
const hookLibSrc = join(hooksSrc, "lib");
|
|
3952
|
-
const hookLibFiles = await pathExists(hookLibSrc) ? await Promise.all((await listFiles(hookLibSrc)).filter((rel) => rel.endsWith(".sh")).map((rel) => copyFileEntry(join(hookLibSrc, rel), join(
|
|
3958
|
+
const hookLibFiles = await pathExists(hookLibSrc) ? await Promise.all((await listFiles(hookLibSrc)).filter((rel) => rel.endsWith(".sh")).map((rel) => copyFileEntry(join(hookLibSrc, rel), join(HOOKS_DIR, "lib", rel), true))) : [];
|
|
3953
3959
|
const commandsSrc = join(vendorRoot, "commands");
|
|
3954
|
-
const commandFiles = await pathExists(commandsSrc) ? await Promise.all((await listFiles(commandsSrc)).map(async (rel) => withAssetFrontmatter(await copyFileEntry(join(commandsSrc, rel), join(
|
|
3960
|
+
const commandFiles = await pathExists(commandsSrc) ? await Promise.all((await listFiles(commandsSrc)).map(async (rel) => withAssetFrontmatter(await copyFileEntry(join(commandsSrc, rel), join(COMMANDS_DIR, rel)), vendorVersion))) : [];
|
|
3955
3961
|
const configSchema = await copyFileEntry(join(vendorRoot, HARNESS_CONFIG_SCHEMA_FILENAME), HARNESS_CONFIG_SCHEMA_FILENAME);
|
|
3956
3962
|
return [
|
|
3957
3963
|
entryProtocol,
|
|
@@ -4081,7 +4087,7 @@ const diffLostCommands = (existingHooks, templateHooks) => {
|
|
|
4081
4087
|
//#endregion
|
|
4082
4088
|
//#region src/install/resync.ts
|
|
4083
4089
|
const resyncSpecialCased = async (repoRoot, templateRoot) => {
|
|
4084
|
-
const { lostCommands } = await mergeSettingsHooks(join(templateRoot,
|
|
4090
|
+
const { lostCommands } = await mergeSettingsHooks(join(templateRoot, CLAUDE_DIR, "settings.json.tpl"), join(repoRoot, SETTINGS_FILE));
|
|
4085
4091
|
return {
|
|
4086
4092
|
lostHookCommands: lostCommands,
|
|
4087
4093
|
gitignorePath: await appendGitignoreBlock(repoRoot)
|
|
@@ -4128,11 +4134,11 @@ const runInstall = async (options) => {
|
|
|
4128
4134
|
});
|
|
4129
4135
|
await assertNoSymlinkTraversal(repoRoot, [
|
|
4130
4136
|
...vendorFiles.map((file) => file.relPath),
|
|
4131
|
-
|
|
4137
|
+
SETTINGS_FILE,
|
|
4132
4138
|
".gitignore",
|
|
4133
|
-
join(
|
|
4134
|
-
join(
|
|
4135
|
-
...COMMITTED_STATE_DIRS.map((dir) => join(
|
|
4139
|
+
join(STATE_DIR, "history.md"),
|
|
4140
|
+
join(STATE_DIR, "sessions"),
|
|
4141
|
+
...COMMITTED_STATE_DIRS.map((dir) => join(STATE_DIR, dir, ".gitkeep")),
|
|
4136
4142
|
HARNESS_CONFIG_FILENAME
|
|
4137
4143
|
]);
|
|
4138
4144
|
const readClient = async (relPath) => {
|
|
@@ -4186,9 +4192,8 @@ const runInstall = async (options) => {
|
|
|
4186
4192
|
await writeBaseline(repoRoot, vendorVersion, vendorFiles);
|
|
4187
4193
|
const claudeMdPresent = await pathExists(join(repoRoot, CLAUDE_MD_FILENAME));
|
|
4188
4194
|
const devDependencyMissing = await inspectDevDependency(repoRoot) === "missing";
|
|
4189
|
-
const stateFiles = await Promise.all([writeOut(repoRoot, join(
|
|
4190
|
-
await mkdir(join(repoRoot,
|
|
4191
|
-
const settingsRelPath = join(".claude", "settings.json");
|
|
4195
|
+
const stateFiles = await Promise.all([writeOut(repoRoot, join(STATE_DIR, "history.md"), renderTemplate(await readFile(join(templateRoot, "state", "history.md.tpl"), "utf8"), vars)), ...COMMITTED_STATE_DIRS.map((dir) => writeOut(repoRoot, join(STATE_DIR, dir, ".gitkeep"), ""))]);
|
|
4196
|
+
await mkdir(join(repoRoot, STATE_DIR, "sessions"), { recursive: true });
|
|
4192
4197
|
const { lostHookCommands, gitignorePath: gitignore } = await resyncSpecialCased(repoRoot, templateRoot);
|
|
4193
4198
|
const configFile = await writeOut(repoRoot, HARNESS_CONFIG_FILENAME, renderTemplate(await readFile(join(templateRoot, "harness.config.yml.tpl"), "utf8"), vars));
|
|
4194
4199
|
return {
|
|
@@ -4201,7 +4206,7 @@ const runInstall = async (options) => {
|
|
|
4201
4206
|
filesWritten: [
|
|
4202
4207
|
...vendorPaths,
|
|
4203
4208
|
...stateFiles,
|
|
4204
|
-
|
|
4209
|
+
SETTINGS_FILE,
|
|
4205
4210
|
...gitignore ? [gitignore] : [],
|
|
4206
4211
|
configFile
|
|
4207
4212
|
],
|
|
@@ -4276,7 +4281,7 @@ const runReconcile = async (inputs) => {
|
|
|
4276
4281
|
await refuseOnResidualMarkers(managedPaths, readClient);
|
|
4277
4282
|
await assertNoSymlinkTraversal(repoRoot, [
|
|
4278
4283
|
...managedPaths,
|
|
4279
|
-
|
|
4284
|
+
SETTINGS_FILE,
|
|
4280
4285
|
".gitignore"
|
|
4281
4286
|
]);
|
|
4282
4287
|
const actions = await runEngine({
|
|
@@ -4417,8 +4422,6 @@ const runRepair = async (options) => {
|
|
|
4417
4422
|
};
|
|
4418
4423
|
//#endregion
|
|
4419
4424
|
//#region src/uninstall/uninstall.ts
|
|
4420
|
-
const HARNESS_DIR = join(".claude", ".harness");
|
|
4421
|
-
const SETTINGS_RELPATH = join(".claude", "settings.json");
|
|
4422
4425
|
const DOCS_PREFIX = `docs${sep}`;
|
|
4423
4426
|
const runUninstall = async (options) => {
|
|
4424
4427
|
const { repoRoot } = options;
|
|
@@ -4429,14 +4432,14 @@ const runUninstall = async (options) => {
|
|
|
4429
4432
|
const removedFiles = [...(await readBaseline(repoRoot, baselineVersion)).keys()].filter((relPath) => !relPath.startsWith(DOCS_PREFIX)).toSorted();
|
|
4430
4433
|
await assertNoSymlinkTraversal(repoRoot, [
|
|
4431
4434
|
...removedFiles,
|
|
4432
|
-
|
|
4435
|
+
SETTINGS_FILE,
|
|
4433
4436
|
".gitignore",
|
|
4434
4437
|
HARNESS_DIR,
|
|
4435
4438
|
HARNESS_CONFIG_FILENAME
|
|
4436
4439
|
]);
|
|
4437
4440
|
await Promise.all(removedFiles.map((relPath) => rm(join(repoRoot, relPath), { force: true })));
|
|
4438
4441
|
await pruneEmptyDirs(repoRoot, removedFiles);
|
|
4439
|
-
const settings = await removeSettingsHooks(join(repoRoot,
|
|
4442
|
+
const settings = await removeSettingsHooks(join(repoRoot, SETTINGS_FILE));
|
|
4440
4443
|
const gitignoreStripped = await removeGitignoreBlock(repoRoot) !== null;
|
|
4441
4444
|
await rm(join(repoRoot, HARNESS_DIR), {
|
|
4442
4445
|
recursive: true,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lemoncode/lemony",
|
|
3
|
-
"version": "0.1.1-alpha.
|
|
3
|
+
"version": "0.1.1-alpha.2",
|
|
4
4
|
"description": "Lemony — a Harness for AI Coding. Vendor package: installer, agent role catalog, generic skill catalog, hooks, and templates for a Spec-Driven Development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|