@lemoncode/lemony 0.1.1-alpha.1 → 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/catalog/VERSION +1 -1
- package/catalog/harness.config.schema.json +0 -12
- package/catalog/skills/grill-ui/SKILL.md +1 -0
- 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/harness.config.yml.tpl +0 -3
- package/dist/cli.mjs +54 -41
- package/package.json +1 -1
package/NOTICE
CHANGED
|
@@ -12,11 +12,13 @@ The catalog components below adapt text or code from the following MIT-licensed
|
|
|
12
12
|
sources. Each source's copyright notice is reproduced here; the shared MIT
|
|
13
13
|
permission notice (reproduced once, at the end of this section) applies to each.
|
|
14
14
|
|
|
15
|
-
- **mattpocock/skills** — Copyright (c) Matt Pocock
|
|
15
|
+
- **mattpocock/skills** — Copyright (c) 2026 Matt Pocock
|
|
16
16
|
Source: https://github.com/mattpocock/skills
|
|
17
17
|
Adapted by:
|
|
18
18
|
- grill-ui — grill interview engine — one question at a time, decision-by-decision interrogation
|
|
19
19
|
- grill-with-docs — grill workflow and decision-by-decision interview structure
|
|
20
|
+
- tdd — the red-green cycle, the behavior-over-implementation core principle, the vertical-slice ("tracer bullet") framing, mock-at-system-boundaries guidance, and the ADR three-tests nudge
|
|
21
|
+
- write-adr — ADR format — the three-tests rubric (hard to reverse / surprising / real trade-off), the record template, and the "what qualifies" guidance
|
|
20
22
|
|
|
21
23
|
### MIT License (applies to each source listed above)
|
|
22
24
|
|
package/catalog/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.1.1-alpha.
|
|
1
|
+
0.1.1-alpha.2
|
|
@@ -36,18 +36,6 @@
|
|
|
36
36
|
"default": {},
|
|
37
37
|
"type": "object",
|
|
38
38
|
"properties": {
|
|
39
|
-
"state": {
|
|
40
|
-
"default": ".claude/state",
|
|
41
|
-
"type": "string"
|
|
42
|
-
},
|
|
43
|
-
"skills": {
|
|
44
|
-
"default": ".claude/skills",
|
|
45
|
-
"type": "string"
|
|
46
|
-
},
|
|
47
|
-
"agents": {
|
|
48
|
-
"default": ".claude/agents",
|
|
49
|
-
"type": "string"
|
|
50
|
-
},
|
|
51
39
|
"playbooks": {
|
|
52
40
|
"default": "docs/playbooks",
|
|
53
41
|
"type": "string"
|
|
@@ -9,6 +9,7 @@ attribution:
|
|
|
9
9
|
author: Matt Pocock
|
|
10
10
|
url: https://github.com/mattpocock/skills
|
|
11
11
|
license: MIT
|
|
12
|
+
copyright: Copyright (c) 2026 Matt Pocock
|
|
12
13
|
relationship: derived-from
|
|
13
14
|
note: grill interview engine — one question at a time, decision-by-decision interrogation
|
|
14
15
|
---
|
|
@@ -5,6 +5,14 @@ origin: vendor
|
|
|
5
5
|
vendor_version: '{{vendor_version}}'
|
|
6
6
|
phase: during-implementation
|
|
7
7
|
invoked-by: [implementer]
|
|
8
|
+
attribution:
|
|
9
|
+
- source: mattpocock/skills
|
|
10
|
+
author: Matt Pocock
|
|
11
|
+
url: https://github.com/mattpocock/skills
|
|
12
|
+
license: MIT
|
|
13
|
+
copyright: Copyright (c) 2026 Matt Pocock
|
|
14
|
+
relationship: derived-from
|
|
15
|
+
note: the red-green cycle, the behavior-over-implementation core principle, the vertical-slice ("tracer bullet") framing, mock-at-system-boundaries guidance, and the ADR three-tests nudge
|
|
8
16
|
---
|
|
9
17
|
|
|
10
18
|
# Test-Driven Development
|
|
@@ -5,6 +5,14 @@ origin: vendor
|
|
|
5
5
|
vendor_version: '{{vendor_version}}'
|
|
6
6
|
invoked-by: [architect]
|
|
7
7
|
trigger-condition: a resolved decision is worth recording (a discovery resolution, or an explicit request)
|
|
8
|
+
attribution:
|
|
9
|
+
- source: mattpocock/skills
|
|
10
|
+
author: Matt Pocock
|
|
11
|
+
url: https://github.com/mattpocock/skills
|
|
12
|
+
license: MIT
|
|
13
|
+
copyright: Copyright (c) 2026 Matt Pocock
|
|
14
|
+
relationship: derived-from
|
|
15
|
+
note: ADR format — the three-tests rubric (hard to reverse / surprising / real trade-off), the record template, and the "what qualifies" guidance
|
|
8
16
|
---
|
|
9
17
|
|
|
10
18
|
# Write ADR
|
|
@@ -19,9 +19,6 @@ task_storage:
|
|
|
19
19
|
|
|
20
20
|
# Paths (relative; defaults shown — declare only overrides).
|
|
21
21
|
paths:
|
|
22
|
-
state: .claude/state
|
|
23
|
-
skills: .claude/skills
|
|
24
|
-
agents: .claude/agents
|
|
25
22
|
# Project-local playbooks (how-to-build patterns). Looked up by convention
|
|
26
23
|
# `<topic>.md`, local then global; the local entry overrides the global one.
|
|
27
24
|
# The require/suggest hooks read these two keys directly on every fire, so a
|
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";
|
|
@@ -18,6 +18,11 @@ const appendEvent = async (eventsPath, event) => {
|
|
|
18
18
|
//#region src/config/config.constant.ts
|
|
19
19
|
const HARNESS_CONFIG_FILENAME = "harness.config.yml";
|
|
20
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"];
|
|
@@ -26,9 +31,6 @@ const TASK_STORAGE_REPO_PATTERN = /^[^\s/]+\/[^\s/]+$/;
|
|
|
26
31
|
const VENDOR_VERSION_REGEX = /^\d+\.\d+\.\d+(-(alpha|beta|rc)\.\d+)?$/;
|
|
27
32
|
const VENDOR_VERSION_EXAMPLE = "0.1.0-alpha.0";
|
|
28
33
|
const PATHS_DEFAULTS = {
|
|
29
|
-
state: ".claude/state",
|
|
30
|
-
skills: ".claude/skills",
|
|
31
|
-
agents: ".claude/agents",
|
|
32
34
|
playbooks: "docs/playbooks",
|
|
33
35
|
playbooks_global: "~/.claude/playbooks"
|
|
34
36
|
};
|
|
@@ -97,9 +99,6 @@ const levenshtein = (a, b) => {
|
|
|
97
99
|
//#endregion
|
|
98
100
|
//#region src/config/config.schema.ts
|
|
99
101
|
const pathsSchema = z.object({
|
|
100
|
-
state: z.string().default(PATHS_DEFAULTS.state),
|
|
101
|
-
skills: z.string().default(PATHS_DEFAULTS.skills),
|
|
102
|
-
agents: z.string().default(PATHS_DEFAULTS.agents),
|
|
103
102
|
playbooks: z.string().default(PATHS_DEFAULTS.playbooks),
|
|
104
103
|
playbooks_global: z.string().default(PATHS_DEFAULTS.playbooks_global)
|
|
105
104
|
}).strict().prefault({});
|
|
@@ -139,6 +138,12 @@ const readHarnessConfig = async (repoRoot) => {
|
|
|
139
138
|
if (!parsed || typeof parsed !== "object") throw new Error(`${HARNESS_CONFIG_FILENAME} is empty or malformed.`);
|
|
140
139
|
const candidate = { ...parsed };
|
|
141
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
|
+
}
|
|
142
147
|
const result = harnessConfigSchema.safeParse(candidate);
|
|
143
148
|
if (!result.success) throw new Error(`${HARNESS_CONFIG_FILENAME} is invalid:\n${formatConfigError(harnessConfigSchema, result.error)}`);
|
|
144
149
|
return result.data;
|
|
@@ -194,6 +199,9 @@ const setConfigValues = (rawYaml, updates) => {
|
|
|
194
199
|
const doc = parseDocument(rawYaml);
|
|
195
200
|
for (const [key, value] of Object.entries(updates)) if (doc.has(key)) doc.set(key, value);
|
|
196
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");
|
|
197
205
|
return doc.toString();
|
|
198
206
|
};
|
|
199
207
|
const writeConfigValues = async (repoRoot, updates) => {
|
|
@@ -216,6 +224,19 @@ const pointerFrontmatterSchema = z.object({
|
|
|
216
224
|
});
|
|
217
225
|
Object.keys(pointerFrontmatterSchema.shape);
|
|
218
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
|
|
219
240
|
//#region src/events/envelope.ts
|
|
220
241
|
const buildEnvelope = (input) => {
|
|
221
242
|
const ts = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
@@ -389,7 +410,6 @@ const NUMERIC_FIELDS = /* @__PURE__ */ new Set([
|
|
|
389
410
|
"review_iterations"
|
|
390
411
|
]);
|
|
391
412
|
const BOOLEAN_FIELDS = /* @__PURE__ */ new Set(["auto_close"]);
|
|
392
|
-
const EVENTS_RELPATH$1 = join(".claude", "state", "events.jsonl");
|
|
393
413
|
const isEventType = (value) => EVENT_TYPES.includes(value);
|
|
394
414
|
const coerceField = (key, raw) => {
|
|
395
415
|
if (NUMERIC_FIELDS.has(key)) {
|
|
@@ -444,7 +464,7 @@ const runEmit = async (options) => {
|
|
|
444
464
|
const issues = parsed.error.issues.map((issue) => ` - ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
|
|
445
465
|
throw new Error(`Event validation failed for type "${rawType}":\n${issues}`);
|
|
446
466
|
}
|
|
447
|
-
const eventsPath = join(repoRoot, EVENTS_RELPATH
|
|
467
|
+
const eventsPath = join(repoRoot, EVENTS_RELPATH);
|
|
448
468
|
await appendEvent(eventsPath, parsed.data);
|
|
449
469
|
return {
|
|
450
470
|
event: parsed.data,
|
|
@@ -2211,9 +2231,8 @@ const sanitizeLine = (raw, tier) => {
|
|
|
2211
2231
|
};
|
|
2212
2232
|
//#endregion
|
|
2213
2233
|
//#region src/telemetry/telemetry.constant.ts
|
|
2214
|
-
const
|
|
2215
|
-
const
|
|
2216
|
-
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");
|
|
2217
2236
|
const PAYLOAD_CAP_BYTES = 1e6;
|
|
2218
2237
|
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
2219
2238
|
const PRUNE_THRESHOLD_BYTES = 5e6;
|
|
@@ -2275,7 +2294,7 @@ const chunkTail = (rawTail, tier, capBytes = PAYLOAD_CAP_BYTES) => {
|
|
|
2275
2294
|
};
|
|
2276
2295
|
//#endregion
|
|
2277
2296
|
//#region src/telemetry/consent.constant.ts
|
|
2278
|
-
const CONSENT_RELPATH = join(
|
|
2297
|
+
const CONSENT_RELPATH = join(STATE_DIR, "telemetry-consent.json");
|
|
2279
2298
|
const ENV_DISABLE_VALUES = [
|
|
2280
2299
|
"1",
|
|
2281
2300
|
"true",
|
|
@@ -2671,7 +2690,7 @@ const formatTelemetryShow = (report) => {
|
|
|
2671
2690
|
};
|
|
2672
2691
|
//#endregion
|
|
2673
2692
|
//#region src/telemetry/telemetry-notice.constant.ts
|
|
2674
|
-
const NOTICE_SENTINEL_RELPATH = join(
|
|
2693
|
+
const NOTICE_SENTINEL_RELPATH = join(STATE_DIR, ".telemetry-notice-shown");
|
|
2675
2694
|
const NOTICE_MESSAGE = "Anonymous telemetry is ON — run `lemony telemetry disable` to opt out. See PRIVACY.md.";
|
|
2676
2695
|
//#endregion
|
|
2677
2696
|
//#region src/telemetry/telemetry-notice.ts
|
|
@@ -3061,7 +3080,7 @@ const checkLabels = async (deps, config) => {
|
|
|
3061
3080
|
};
|
|
3062
3081
|
const checkHooks = async (repoRoot) => {
|
|
3063
3082
|
const name = "hooks";
|
|
3064
|
-
const settingsPath = join(repoRoot,
|
|
3083
|
+
const settingsPath = join(repoRoot, SETTINGS_FILE);
|
|
3065
3084
|
let raw;
|
|
3066
3085
|
try {
|
|
3067
3086
|
raw = await readFile(settingsPath, "utf8");
|
|
@@ -3317,7 +3336,7 @@ const readPointer = async (repoRoot, readGitUserEmail) => {
|
|
|
3317
3336
|
if (!email) return empty;
|
|
3318
3337
|
const slug = email.split("@")[0] ?? "";
|
|
3319
3338
|
if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..")) return empty;
|
|
3320
|
-
const pointerPath = join(repoRoot,
|
|
3339
|
+
const pointerPath = join(repoRoot, STATE_DIR, `current-${slug}.md`);
|
|
3321
3340
|
let raw;
|
|
3322
3341
|
try {
|
|
3323
3342
|
raw = await readFile(pointerPath, "utf8");
|
|
@@ -3450,7 +3469,6 @@ const renderTemplate = (template, vars) => template.replace(PLACEHOLDER, (_match
|
|
|
3450
3469
|
});
|
|
3451
3470
|
//#endregion
|
|
3452
3471
|
//#region src/baseline/baseline.ts
|
|
3453
|
-
const BASELINE_ROOT_REL = join(".claude", ".harness", "baseline");
|
|
3454
3472
|
const STAGING_DIR$1 = ".staging";
|
|
3455
3473
|
const isENOENT$1 = (error) => typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
3456
3474
|
const baselineRootDir = (repoRoot) => join(repoRoot, BASELINE_ROOT_REL);
|
|
@@ -3515,7 +3533,6 @@ const findBaselineVersion = async (repoRoot) => {
|
|
|
3515
3533
|
};
|
|
3516
3534
|
//#endregion
|
|
3517
3535
|
//#region src/snapshot/snapshot.ts
|
|
3518
|
-
const SNAPSHOTS_ROOT_REL = join(".claude", ".harness", "snapshots");
|
|
3519
3536
|
const STAGING_DIR = ".staging";
|
|
3520
3537
|
const WORKING_SUBDIR = "working";
|
|
3521
3538
|
const BASELINE_SUBDIR = "baseline";
|
|
@@ -3775,12 +3792,11 @@ const lcsMatches = (base, other) => {
|
|
|
3775
3792
|
};
|
|
3776
3793
|
//#endregion
|
|
3777
3794
|
//#region src/update/engine.ts
|
|
3778
|
-
const SKILLS_PREFIX = join(".claude", "skills");
|
|
3779
3795
|
const cohesionKey = (relPath) => {
|
|
3780
|
-
const prefix =
|
|
3796
|
+
const prefix = SKILLS_DIR + sep;
|
|
3781
3797
|
if (!relPath.startsWith(prefix)) return relPath;
|
|
3782
3798
|
const name = relPath.slice(prefix.length).split(sep)[0];
|
|
3783
|
-
return name ? join(
|
|
3799
|
+
return name ? join(SKILLS_DIR, name) : relPath;
|
|
3784
3800
|
};
|
|
3785
3801
|
const runEngine = async (inputs) => {
|
|
3786
3802
|
const { baseline, readClient, onConflict } = inputs;
|
|
@@ -3925,23 +3941,23 @@ const materializeVendorFiles = async (ctx) => {
|
|
|
3925
3941
|
const templateRoot = join(vendorRoot, "templates", target);
|
|
3926
3942
|
const vendorVersion = vars.vendor_version ?? "";
|
|
3927
3943
|
const entryProtocol = await renderFile(join(templateRoot, "agents.md.tpl"), "agents.md", vars);
|
|
3928
|
-
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`), {
|
|
3929
3945
|
SKILLS: renderRoleSkills(skills, role),
|
|
3930
3946
|
vendor_version: vendorVersion
|
|
3931
3947
|
}), vendorVersion)));
|
|
3932
|
-
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"));
|
|
3933
3949
|
const skillFiles = (await Promise.all(skills.map(async (skill) => {
|
|
3934
3950
|
const skillDir = join(vendorRoot, "skills", skill.name);
|
|
3935
3951
|
const rels = await listFiles(skillDir);
|
|
3936
|
-
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)));
|
|
3937
3953
|
}))).flat();
|
|
3938
3954
|
const playbooksReadme = await renderFile(join(templateRoot, "docs", "playbooks", "README.md.tpl"), join("docs", "playbooks", "README.md"), vars);
|
|
3939
3955
|
const hooksSrc = join(vendorRoot, "hooks");
|
|
3940
|
-
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)));
|
|
3941
3957
|
const hookLibSrc = join(hooksSrc, "lib");
|
|
3942
|
-
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))) : [];
|
|
3943
3959
|
const commandsSrc = join(vendorRoot, "commands");
|
|
3944
|
-
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))) : [];
|
|
3945
3961
|
const configSchema = await copyFileEntry(join(vendorRoot, HARNESS_CONFIG_SCHEMA_FILENAME), HARNESS_CONFIG_SCHEMA_FILENAME);
|
|
3946
3962
|
return [
|
|
3947
3963
|
entryProtocol,
|
|
@@ -4071,7 +4087,7 @@ const diffLostCommands = (existingHooks, templateHooks) => {
|
|
|
4071
4087
|
//#endregion
|
|
4072
4088
|
//#region src/install/resync.ts
|
|
4073
4089
|
const resyncSpecialCased = async (repoRoot, templateRoot) => {
|
|
4074
|
-
const { lostCommands } = await mergeSettingsHooks(join(templateRoot,
|
|
4090
|
+
const { lostCommands } = await mergeSettingsHooks(join(templateRoot, CLAUDE_DIR, "settings.json.tpl"), join(repoRoot, SETTINGS_FILE));
|
|
4075
4091
|
return {
|
|
4076
4092
|
lostHookCommands: lostCommands,
|
|
4077
4093
|
gitignorePath: await appendGitignoreBlock(repoRoot)
|
|
@@ -4118,11 +4134,11 @@ const runInstall = async (options) => {
|
|
|
4118
4134
|
});
|
|
4119
4135
|
await assertNoSymlinkTraversal(repoRoot, [
|
|
4120
4136
|
...vendorFiles.map((file) => file.relPath),
|
|
4121
|
-
|
|
4137
|
+
SETTINGS_FILE,
|
|
4122
4138
|
".gitignore",
|
|
4123
|
-
join(
|
|
4124
|
-
join(
|
|
4125
|
-
...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")),
|
|
4126
4142
|
HARNESS_CONFIG_FILENAME
|
|
4127
4143
|
]);
|
|
4128
4144
|
const readClient = async (relPath) => {
|
|
@@ -4176,9 +4192,8 @@ const runInstall = async (options) => {
|
|
|
4176
4192
|
await writeBaseline(repoRoot, vendorVersion, vendorFiles);
|
|
4177
4193
|
const claudeMdPresent = await pathExists(join(repoRoot, CLAUDE_MD_FILENAME));
|
|
4178
4194
|
const devDependencyMissing = await inspectDevDependency(repoRoot) === "missing";
|
|
4179
|
-
const stateFiles = await Promise.all([writeOut(repoRoot, join(
|
|
4180
|
-
await mkdir(join(repoRoot,
|
|
4181
|
-
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 });
|
|
4182
4197
|
const { lostHookCommands, gitignorePath: gitignore } = await resyncSpecialCased(repoRoot, templateRoot);
|
|
4183
4198
|
const configFile = await writeOut(repoRoot, HARNESS_CONFIG_FILENAME, renderTemplate(await readFile(join(templateRoot, "harness.config.yml.tpl"), "utf8"), vars));
|
|
4184
4199
|
return {
|
|
@@ -4191,7 +4206,7 @@ const runInstall = async (options) => {
|
|
|
4191
4206
|
filesWritten: [
|
|
4192
4207
|
...vendorPaths,
|
|
4193
4208
|
...stateFiles,
|
|
4194
|
-
|
|
4209
|
+
SETTINGS_FILE,
|
|
4195
4210
|
...gitignore ? [gitignore] : [],
|
|
4196
4211
|
configFile
|
|
4197
4212
|
],
|
|
@@ -4266,7 +4281,7 @@ const runReconcile = async (inputs) => {
|
|
|
4266
4281
|
await refuseOnResidualMarkers(managedPaths, readClient);
|
|
4267
4282
|
await assertNoSymlinkTraversal(repoRoot, [
|
|
4268
4283
|
...managedPaths,
|
|
4269
|
-
|
|
4284
|
+
SETTINGS_FILE,
|
|
4270
4285
|
".gitignore"
|
|
4271
4286
|
]);
|
|
4272
4287
|
const actions = await runEngine({
|
|
@@ -4407,8 +4422,6 @@ const runRepair = async (options) => {
|
|
|
4407
4422
|
};
|
|
4408
4423
|
//#endregion
|
|
4409
4424
|
//#region src/uninstall/uninstall.ts
|
|
4410
|
-
const HARNESS_DIR = join(".claude", ".harness");
|
|
4411
|
-
const SETTINGS_RELPATH = join(".claude", "settings.json");
|
|
4412
4425
|
const DOCS_PREFIX = `docs${sep}`;
|
|
4413
4426
|
const runUninstall = async (options) => {
|
|
4414
4427
|
const { repoRoot } = options;
|
|
@@ -4419,14 +4432,14 @@ const runUninstall = async (options) => {
|
|
|
4419
4432
|
const removedFiles = [...(await readBaseline(repoRoot, baselineVersion)).keys()].filter((relPath) => !relPath.startsWith(DOCS_PREFIX)).toSorted();
|
|
4420
4433
|
await assertNoSymlinkTraversal(repoRoot, [
|
|
4421
4434
|
...removedFiles,
|
|
4422
|
-
|
|
4435
|
+
SETTINGS_FILE,
|
|
4423
4436
|
".gitignore",
|
|
4424
4437
|
HARNESS_DIR,
|
|
4425
4438
|
HARNESS_CONFIG_FILENAME
|
|
4426
4439
|
]);
|
|
4427
4440
|
await Promise.all(removedFiles.map((relPath) => rm(join(repoRoot, relPath), { force: true })));
|
|
4428
4441
|
await pruneEmptyDirs(repoRoot, removedFiles);
|
|
4429
|
-
const settings = await removeSettingsHooks(join(repoRoot,
|
|
4442
|
+
const settings = await removeSettingsHooks(join(repoRoot, SETTINGS_FILE));
|
|
4430
4443
|
const gitignoreStripped = await removeGitignoreBlock(repoRoot) !== null;
|
|
4431
4444
|
await rm(join(repoRoot, HARNESS_DIR), {
|
|
4432
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,
|