@hone-ai/cli 1.15.0 → 1.17.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/hone-cli.js +42 -10
- package/lib/splice-repo-specific.js +105 -24
- package/package.json +1 -1
package/hone-cli.js
CHANGED
|
@@ -1112,24 +1112,41 @@ program
|
|
|
1112
1112
|
console.log('Writing generated skills...');
|
|
1113
1113
|
if (result.skills) {
|
|
1114
1114
|
let preservedCount = 0;
|
|
1115
|
+
let sidecarCount = 0;
|
|
1115
1116
|
for (const [skillName, content] of Object.entries(result.skills)) {
|
|
1116
1117
|
if (!content || content.length < 50) continue;
|
|
1117
1118
|
const skillDir = path.join(repoRoot, '.github', 'skills', skillName);
|
|
1118
1119
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
1119
1120
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
1120
|
-
const
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1121
|
+
const spliceResult = splicePreserveRepoSpecific(skillFile, content);
|
|
1122
|
+
if (spliceResult.sidecar) {
|
|
1123
|
+
// HC-019y-followup-2: legacy unmarked file — write to sidecar,
|
|
1124
|
+
// don't overwrite, prompt adopter to review.
|
|
1125
|
+
fs.writeFileSync(spliceResult.sidecarPath, spliceResult.merged);
|
|
1126
|
+
sidecarCount++;
|
|
1127
|
+
console.log(` ⚠ .github/skills/${skillName}/SKILL.md — NOT overwritten`);
|
|
1128
|
+
console.log(` Wrote derive output to: ${path.relative(repoRoot, spliceResult.sidecarPath)}`);
|
|
1129
|
+
console.log(` Reason: ${spliceResult.reason}`);
|
|
1125
1130
|
} else {
|
|
1126
|
-
|
|
1131
|
+
fs.writeFileSync(skillFile, spliceResult.merged);
|
|
1132
|
+
if (spliceResult.preserved) {
|
|
1133
|
+
preservedCount++;
|
|
1134
|
+
console.log(` ✓ .github/skills/${skillName}/SKILL.md (preserved adopter REPO-SPECIFIC content)`);
|
|
1135
|
+
} else {
|
|
1136
|
+
console.log(` ✓ .github/skills/${skillName}/SKILL.md`);
|
|
1137
|
+
}
|
|
1127
1138
|
}
|
|
1128
1139
|
}
|
|
1129
1140
|
if (preservedCount > 0) {
|
|
1130
1141
|
console.log(`\n Preserved adopter REPO-SPECIFIC content in ${preservedCount} skill(s).`);
|
|
1131
1142
|
console.log(` (HC-019y-followup-1: derive no longer overwrites curated REPO-SPECIFIC sections)`);
|
|
1132
1143
|
}
|
|
1144
|
+
if (sidecarCount > 0) {
|
|
1145
|
+
console.log(`\n ⚠ HC-019y-followup-2: ${sidecarCount} legacy unmarked SKILL(s) routed to .derive-new sidecars.`);
|
|
1146
|
+
console.log(` Review each sidecar; if the derive output is acceptable, replace the original.`);
|
|
1147
|
+
console.log(` To opt into automatic splice protection on the next derive, add a`);
|
|
1148
|
+
console.log(` '<!-- REPO-SPECIFIC -->' marker to the original file.`);
|
|
1149
|
+
}
|
|
1133
1150
|
}
|
|
1134
1151
|
|
|
1135
1152
|
// H-022: surface parser warnings so silent drops become VISIBLE failures.
|
|
@@ -4503,7 +4520,15 @@ program
|
|
|
4503
4520
|
// blow context with prose)
|
|
4504
4521
|
if (issueBodyForFiles || opts.includePaths) {
|
|
4505
4522
|
try {
|
|
4506
|
-
|
|
4523
|
+
// HC-019n-followup-13c: word-boundary `\b` at end of extension
|
|
4524
|
+
// group so `.jsonl` doesn't match as `.js` and `.tsx` doesn't
|
|
4525
|
+
// match as `.ts`. Pre-fix, both `logs/calibration_runs.jsonl`
|
|
4526
|
+
// and `src/web/app.tsx` got truncated. The truncated paths
|
|
4527
|
+
// failed the downstream `fs.existsSync` check so they didn't
|
|
4528
|
+
// crash the bundling, but they polluted the regex output set
|
|
4529
|
+
// and would have produced confusing "file not found" warnings
|
|
4530
|
+
// if we ever tightened the diagnostic logging.
|
|
4531
|
+
const FILE_PATH_RE = /(?<![\w/])([a-zA-Z_][\w-]*\/(?:[\w.-]+\/)*[\w.-]+\.(?:py|js|ts|jsx|tsx|go|rs|java|rb|sh|sql))\b/g;
|
|
4507
4532
|
const candidates = new Set();
|
|
4508
4533
|
if (issueBodyForFiles) {
|
|
4509
4534
|
for (const m of issueBodyForFiles.matchAll(FILE_PATH_RE)) candidates.add(m[1]);
|
|
@@ -4514,9 +4539,16 @@ program
|
|
|
4514
4539
|
candidates.add(p);
|
|
4515
4540
|
}
|
|
4516
4541
|
}
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
|
|
4542
|
+
// HC-019n-followup-13d: bump caps. Pre-fix MAX_FILES=10 /
|
|
4543
|
+
// MAX_TOTAL_CHARS=50_000 / MAX_PER_FILE_CHARS=10_000 was overly
|
|
4544
|
+
// conservative — OptionsFlow #96 hit 49,783/50,000 with 6 files
|
|
4545
|
+
// (217 chars to spare), meaning the next slightly-larger story
|
|
4546
|
+
// silently dropped files. LLM context windows are 200K+; the
|
|
4547
|
+
// bundle should be able to fill ~25% of that without surprise.
|
|
4548
|
+
// New caps: 30 / 150_000 / 25_000.
|
|
4549
|
+
const MAX_FILES = 30;
|
|
4550
|
+
const MAX_TOTAL_CHARS = 150_000;
|
|
4551
|
+
const MAX_PER_FILE_CHARS = 25_000;
|
|
4520
4552
|
const codebaseFiles = [];
|
|
4521
4553
|
let totalChars = 0;
|
|
4522
4554
|
for (const candidate of candidates) {
|
|
@@ -6,6 +6,24 @@
|
|
|
6
6
|
* (everything below the `<!-- REPO-SPECIFIC -->` marker) is NEVER lost
|
|
7
7
|
* across derive runs.
|
|
8
8
|
*
|
|
9
|
+
* HC-019y-followup-2: legacy adopter-curated SKILLs WITHOUT the
|
|
10
|
+
* REPO-SPECIFIC marker were still vulnerable. OptionsFlow's
|
|
11
|
+
* `python-developer/SKILL.md` (834 lines authored by stories E22-E /
|
|
12
|
+
* E22-A / E16-A predating the marker convention) was REPLACED with
|
|
13
|
+
* 69 lines of fresh LLM output on 2026-05-30 because the file had no
|
|
14
|
+
* marker — the helper fell through to "fresh write" and 765 lines of
|
|
15
|
+
* legitimately-curated content were lost. Restored manually in
|
|
16
|
+
* OptionsFlow PR #106.
|
|
17
|
+
*
|
|
18
|
+
* Fix: non-destructive sidecar mode. When the existing file is
|
|
19
|
+
* substantial (>500 bytes) AND has neither the REPO-SPECIFIC marker
|
|
20
|
+
* nor the ENTERPRISE-MANAGED marker, the helper now returns
|
|
21
|
+
* sidecar=true. The caller writes to `<path>.derive-new` instead of
|
|
22
|
+
* overwriting, emits a warning, and prompts the adopter to add a
|
|
23
|
+
* `<!-- REPO-SPECIFIC -->` marker to opt into splice on the next derive.
|
|
24
|
+
* Same pattern as `setup-ai-pipeline.sh copy_skills()` uses a `.bak`
|
|
25
|
+
* fallback.
|
|
26
|
+
*
|
|
9
27
|
* Pre-fix: derive wrote the file blind (fs.writeFileSync(skillFile,
|
|
10
28
|
* content)). 5 OptionsFlow regression tests broke after a single derive
|
|
11
29
|
* because 378 lines of E33-A/E34-B-added content disappeared.
|
|
@@ -21,41 +39,104 @@
|
|
|
21
39
|
*/
|
|
22
40
|
const fs = require('node:fs');
|
|
23
41
|
|
|
42
|
+
const REPO_SPECIFIC_MARKER = '<!-- REPO-SPECIFIC';
|
|
43
|
+
const ENTERPRISE_MARKER = '<!-- ENTERPRISE-MANAGED';
|
|
44
|
+
|
|
45
|
+
// HC-019y-followup-2: the sidecar threshold. Files smaller than this
|
|
46
|
+
// are treated as stubs / placeholders and overwritten normally — only
|
|
47
|
+
// genuinely substantial unmarked content triggers sidecar mode. 500
|
|
48
|
+
// bytes is roughly: 5-10 lines of meaningful markdown. Tonight's
|
|
49
|
+
// evidence (python-developer/SKILL.md at 834 lines / ~32KB) is well
|
|
50
|
+
// above the threshold; trivial scaffolding (a 1-line stub) is well
|
|
51
|
+
// below. Tunable if real adopter data shows this is wrong.
|
|
52
|
+
const SIDECAR_MIN_BYTES = 500;
|
|
53
|
+
|
|
24
54
|
/**
|
|
25
|
-
* Splice adopter REPO-SPECIFIC tail into derive output
|
|
55
|
+
* Splice adopter REPO-SPECIFIC tail into derive output, OR fall back
|
|
56
|
+
* to sidecar mode for legacy unmarked files (HC-019y-followup-2).
|
|
26
57
|
*
|
|
27
58
|
* @param {string} existingPath absolute path of the file being overwritten
|
|
28
59
|
* @param {string} newContent the content from `hone derive` server response
|
|
29
|
-
* @returns {{
|
|
30
|
-
* merged:
|
|
31
|
-
* preserved:
|
|
32
|
-
*
|
|
60
|
+
* @returns {{
|
|
61
|
+
* merged: string,
|
|
62
|
+
* preserved: boolean,
|
|
63
|
+
* sidecar: boolean,
|
|
64
|
+
* sidecarPath: string|null,
|
|
65
|
+
* reason: string|null
|
|
66
|
+
* }}
|
|
67
|
+
* merged: bytes to write (to existingPath OR sidecarPath)
|
|
68
|
+
* preserved: true if adopter REPO-SPECIFIC content was preserved
|
|
69
|
+
* sidecar: true if caller should write to sidecarPath instead of
|
|
70
|
+
* overwriting existingPath; false otherwise
|
|
71
|
+
* sidecarPath: when sidecar=true, the path the caller should write to
|
|
72
|
+
* (always `${existingPath}.derive-new`); null otherwise
|
|
73
|
+
* reason: when sidecar=true, human-readable explanation for the
|
|
74
|
+
* warning the caller should emit; null otherwise
|
|
33
75
|
*/
|
|
34
76
|
function splicePreserveRepoSpecific(existingPath, newContent) {
|
|
35
|
-
const MARKER = '<!-- REPO-SPECIFIC';
|
|
36
77
|
if (!fs.existsSync(existingPath)) {
|
|
37
78
|
// Fresh adopter — no existing file. Write new content as-is.
|
|
38
|
-
return { merged: newContent, preserved: false };
|
|
79
|
+
return { merged: newContent, preserved: false, sidecar: false, sidecarPath: null, reason: null };
|
|
39
80
|
}
|
|
40
81
|
const existing = fs.readFileSync(existingPath, 'utf8');
|
|
41
|
-
const oldIdx = existing.indexOf(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
//
|
|
45
|
-
|
|
82
|
+
const oldIdx = existing.indexOf(REPO_SPECIFIC_MARKER);
|
|
83
|
+
|
|
84
|
+
if (oldIdx >= 0) {
|
|
85
|
+
// Existing has REPO-SPECIFIC marker — splice as before.
|
|
86
|
+
const newIdx = newContent.indexOf(REPO_SPECIFIC_MARKER);
|
|
87
|
+
if (newIdx < 0) {
|
|
88
|
+
// New content has no REPO-SPECIFIC section. Append the existing one.
|
|
89
|
+
return {
|
|
90
|
+
merged: newContent.trimEnd() + '\n\n' + existing.slice(oldIdx),
|
|
91
|
+
preserved: true,
|
|
92
|
+
sidecar: false,
|
|
93
|
+
sidecarPath: null,
|
|
94
|
+
reason: null,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// Both have REPO-SPECIFIC. Take new content up to the marker, then
|
|
98
|
+
// append everything from existing's marker to EOF.
|
|
99
|
+
return {
|
|
100
|
+
merged: newContent.slice(0, newIdx) + existing.slice(oldIdx),
|
|
101
|
+
preserved: true,
|
|
102
|
+
sidecar: false,
|
|
103
|
+
sidecarPath: null,
|
|
104
|
+
reason: null,
|
|
105
|
+
};
|
|
46
106
|
}
|
|
47
|
-
|
|
48
|
-
if
|
|
49
|
-
|
|
50
|
-
|
|
107
|
+
|
|
108
|
+
// No REPO-SPECIFIC marker. HC-019y-followup-2: check if this is a
|
|
109
|
+
// legacy substantial-unmarked file that we'd risk destroying.
|
|
110
|
+
const hasEnterpriseMarker = existing.indexOf(ENTERPRISE_MARKER) >= 0;
|
|
111
|
+
const isSubstantial = Buffer.byteLength(existing, 'utf8') >= SIDECAR_MIN_BYTES;
|
|
112
|
+
|
|
113
|
+
if (isSubstantial && !hasEnterpriseMarker) {
|
|
114
|
+
// Legacy unmarked file with real content. Write new content to a
|
|
115
|
+
// sidecar path instead of overwriting. Adopter reviews + decides
|
|
116
|
+
// whether to adopt the derive output, manually merge, or opt into
|
|
117
|
+
// splice protection by adding a REPO-SPECIFIC marker.
|
|
118
|
+
return {
|
|
119
|
+
merged: newContent,
|
|
120
|
+
preserved: false,
|
|
121
|
+
sidecar: true,
|
|
122
|
+
sidecarPath: `${existingPath}.derive-new`,
|
|
123
|
+
reason:
|
|
124
|
+
`legacy unmarked file (${Buffer.byteLength(existing, 'utf8')} bytes) — ` +
|
|
125
|
+
`wrote derive output to <path>.derive-new instead of overwriting. ` +
|
|
126
|
+
`Review the sidecar, then either: (a) replace the original, ` +
|
|
127
|
+
`(b) manually merge curated sections, or (c) add ` +
|
|
128
|
+
`'<!-- REPO-SPECIFIC -->' to the original to opt into splice on next derive.`,
|
|
129
|
+
};
|
|
51
130
|
}
|
|
52
|
-
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
return {
|
|
56
|
-
merged: newContent.slice(0, newIdx) + existing.slice(oldIdx),
|
|
57
|
-
preserved: true,
|
|
58
|
-
};
|
|
131
|
+
|
|
132
|
+
// Either small (stub/placeholder) or has ENTERPRISE-MANAGED marker
|
|
133
|
+
// (deliberately enterprise-controlled). Safe to overwrite.
|
|
134
|
+
return { merged: newContent, preserved: false, sidecar: false, sidecarPath: null, reason: null };
|
|
59
135
|
}
|
|
60
136
|
|
|
61
|
-
module.exports = {
|
|
137
|
+
module.exports = {
|
|
138
|
+
splicePreserveRepoSpecific,
|
|
139
|
+
REPO_SPECIFIC_MARKER,
|
|
140
|
+
ENTERPRISE_MARKER,
|
|
141
|
+
SIDECAR_MIN_BYTES,
|
|
142
|
+
};
|