autark-cli 0.5.0 → 0.5.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/autark.mjs +42 -22
- package/package.json +1 -1
package/autark.mjs
CHANGED
|
@@ -515,10 +515,8 @@ async function refreshRuntimeFiles() {
|
|
|
515
515
|
}
|
|
516
516
|
|
|
517
517
|
async function refreshSkills() {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if (res.status !== 0) throw new Error(`skill ${name} install failed`)
|
|
521
|
-
}
|
|
518
|
+
const res = await spawnSync('pnpm', ['dlx', '--silent', 'skills', 'add', 'kiluazen/kstack', '-s', SKILL_NAMES.join(','), '-y', '-g'])
|
|
519
|
+
if (res.status !== 0) throw new Error(`kstack skills install failed`)
|
|
522
520
|
}
|
|
523
521
|
|
|
524
522
|
async function reloadLaunchd() {
|
|
@@ -831,48 +829,52 @@ async function mailLint(rest) {
|
|
|
831
829
|
'the key insight', 'what this means is', 'the answer is', 'the trick is',
|
|
832
830
|
'that\'s exactly', 'that\'s precisely', 'that\'s the whole point',
|
|
833
831
|
]
|
|
832
|
+
const WHY = {
|
|
833
|
+
'structure-word': 'AI-fingerprint vocabulary. Humans don\'t write "structurally different" or "fundamentally" in cold email; LLMs reach for them when hedging or sounding profound. Describe the thing directly instead.',
|
|
834
|
+
'em-dash': 'Em-dashes in cold outreach are a strong AI tell. Real people use commas, periods, or a new line break.',
|
|
835
|
+
'body-url': 'URLs mid-paragraph read like an A/B test variant or a campaign send. The product link belongs in the signature anchor; if it\'s anywhere else, the recipient classifies the email as automated.',
|
|
836
|
+
'too-many-questions': 'Asking >1 question is hedging. Pick the one question that, if answered, gives you the most signal. The rest can wait.',
|
|
837
|
+
'compound-question': 'Compound a/b questions ("are you X or Y...") read as AI hedging across alternatives. Humans ask one specific thing. Drop the "or" and pick the more specific half.',
|
|
838
|
+
'too-long': 'Cold outreach over 400 chars (~4-6 short lines) reads as a pitch deck, not a peer note. If you wrote more, you\'re explaining instead of asking. Cut to the single sharpest sentence.',
|
|
839
|
+
'no-anchor-sig': 'Without a clickable name in the signature, the recipient has to type your name into Google to figure out who you are. The HTML <a href> / markdown [name](url) sig is the single biggest "who is this person?" mitigator. Plain "Kushal" alone is a tell.',
|
|
840
|
+
}
|
|
834
841
|
|
|
835
842
|
const violations = []
|
|
836
843
|
const lower = body.toLowerCase()
|
|
837
844
|
|
|
838
|
-
// 1. structure-words
|
|
839
845
|
for (const w of STRUCTURE_WORDS) {
|
|
840
|
-
if (lower.includes(w)) violations.push({ rule: 'structure-word', detail: w })
|
|
846
|
+
if (lower.includes(w)) violations.push({ rule: 'structure-word', detail: w, why: WHY['structure-word'] })
|
|
841
847
|
}
|
|
842
|
-
|
|
843
|
-
if (/—/.test(body)) violations.push({ rule: 'em-dash', detail: 'use a period or comma instead' })
|
|
844
|
-
// 3. URLs in body (allow only inside markdown anchor [text](url) or HTML <a href>)
|
|
848
|
+
if (/—/.test(body)) violations.push({ rule: 'em-dash', detail: 'em-dash detected', why: WHY['em-dash'] })
|
|
845
849
|
const urlMatches = body.match(/https?:\/\/[^\s<>"')]+/g) || []
|
|
846
850
|
for (const u of urlMatches) {
|
|
847
851
|
const idx = body.indexOf(u)
|
|
848
852
|
const before = body.slice(Math.max(0, idx - 40), idx)
|
|
849
|
-
const after = body.slice(idx + u.length, idx + u.length + 10)
|
|
850
853
|
const insideMdAnchor = /\]\(\s*$/.test(before)
|
|
851
854
|
const insideHtmlAnchor = /href\s*=\s*["'][^"']*$/.test(before) || /<a\b[^>]*$/.test(before)
|
|
852
|
-
if (!insideMdAnchor && !insideHtmlAnchor) violations.push({ rule: 'body-url', detail: u })
|
|
855
|
+
if (!insideMdAnchor && !insideHtmlAnchor) violations.push({ rule: 'body-url', detail: u, why: WHY['body-url'] })
|
|
853
856
|
}
|
|
854
|
-
// 4. multiple question marks (more than one ? in the body)
|
|
855
857
|
const qmarks = (body.match(/\?/g) || []).length
|
|
856
|
-
if (qmarks > 1) violations.push({ rule: 'too-many-questions', detail: `${qmarks} question marks
|
|
857
|
-
// 5. compound question with "or" connecting clauses
|
|
858
|
+
if (qmarks > 1) violations.push({ rule: 'too-many-questions', detail: `${qmarks} question marks`, why: WHY['too-many-questions'] })
|
|
858
859
|
const compoundQ = /\b(are|is|do|does|did|will|would|can|could|should|have|has)\b[^?]{0,200}\bor\b[^?]{0,200}\?/i
|
|
859
|
-
if (compoundQ.test(body)) violations.push({ rule: 'compound-question', detail: 'a/b question with "or"
|
|
860
|
-
// 6. body length (excluding signature heuristically)
|
|
860
|
+
if (compoundQ.test(body)) violations.push({ rule: 'compound-question', detail: 'a/b question with "or"', why: WHY['compound-question'] })
|
|
861
861
|
const sigSplit = body.split(/\n\s*\n(?:best|thanks|cheers|—|-)[\s,]*\n/i)[0] || body
|
|
862
862
|
const len = sigSplit.trim().length
|
|
863
|
-
if (len > 400) violations.push({ rule: 'too-long', detail: `${len} chars
|
|
864
|
-
// 7. signature presence: should have `- <name>` and an anchor (md or html)
|
|
863
|
+
if (len > 400) violations.push({ rule: 'too-long', detail: `${len} chars`, why: WHY['too-long'] })
|
|
865
864
|
const hasMdAnchor = /\[[^\]]+\]\(https?:\/\/[^)]+\)/.test(body)
|
|
866
865
|
const hasHtmlAnchor = /<a\b[^>]*href\s*=\s*["']https?:\/\/[^"']+["'][^>]*>[^<]+<\/a>/i.test(body)
|
|
867
|
-
if (!hasMdAnchor && !hasHtmlAnchor) {
|
|
868
|
-
violations.push({ rule: 'no-anchor-sig', detail: 'signature must include a clickable link via [name](url) or <a href="url">name</a>' })
|
|
869
|
-
}
|
|
866
|
+
if (!hasMdAnchor && !hasHtmlAnchor) violations.push({ rule: 'no-anchor-sig', detail: 'no clickable name in signature', why: WHY['no-anchor-sig'] })
|
|
870
867
|
|
|
871
868
|
if (violations.length === 0) {
|
|
872
869
|
console.log(JSON.stringify({ clean: true, length: len }, null, 2))
|
|
873
870
|
return
|
|
874
871
|
}
|
|
875
|
-
console.error(JSON.stringify({
|
|
872
|
+
console.error(JSON.stringify({
|
|
873
|
+
clean: false,
|
|
874
|
+
length: len,
|
|
875
|
+
violations,
|
|
876
|
+
next: 'Rewrite the draft so each violation\'s "why" is internalized, not just regex-fixed. If you can\'t make it pass after 2 rewrites, abstain — re-read ~/.claude/skills/outreach/SKILL.md AI-tells section and ask whether you should send this at all.',
|
|
877
|
+
}, null, 2))
|
|
876
878
|
process.exit(1)
|
|
877
879
|
}
|
|
878
880
|
|
|
@@ -1161,6 +1163,16 @@ function printHypothesisContext(result) {
|
|
|
1161
1163
|
kv('hypothesis.code', result.hypothesis.code)
|
|
1162
1164
|
kv('hypothesis.title', result.hypothesis.title)
|
|
1163
1165
|
kv('hypothesis.status', result.hypothesis.status)
|
|
1166
|
+
// Feedback — operator nudges left on this hypothesis, oldest first. The
|
|
1167
|
+
// worker returns them; previously the CLI silently dropped them, so agents
|
|
1168
|
+
// never saw the operator's running corrections. Now surface them up top
|
|
1169
|
+
// (before runs/actions) so the agent reads them at the start of every run.
|
|
1170
|
+
for (const f of (result.feedback || [])) {
|
|
1171
|
+
const shortF = actionShortId(f.id)
|
|
1172
|
+
kv(`feedback.${shortF}.created_at`, f.created_at)
|
|
1173
|
+
if (f.action_id) kv(`feedback.${shortF}.action_id`, f.action_id)
|
|
1174
|
+
kv(`feedback.${shortF}.text`, f.text)
|
|
1175
|
+
}
|
|
1164
1176
|
for (const run of (result.runs || [])) {
|
|
1165
1177
|
const shortRun = actionShortId(run.id)
|
|
1166
1178
|
kv(`run.${shortRun}.id`, run.id)
|
|
@@ -1191,6 +1203,14 @@ function printHypothesisContext(result) {
|
|
|
1191
1203
|
// ---- narrative ----
|
|
1192
1204
|
console.log(`# ${result.product.slug}/${result.hypothesis.code} — ${result.hypothesis.title}\n`)
|
|
1193
1205
|
console.log(result.hypothesis.hypothesis_md)
|
|
1206
|
+
if ((result.feedback || []).length > 0) {
|
|
1207
|
+
console.log(`\n\n## Feedback (operator nudges, oldest first)\n`)
|
|
1208
|
+
for (const f of result.feedback) {
|
|
1209
|
+
const when = f.created_at || ''
|
|
1210
|
+
const tag = f.action_id ? ` (on action ${f.action_id})` : ''
|
|
1211
|
+
console.log(`- [${when}]${tag} ${f.text}`)
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1194
1214
|
for (const run of (result.runs || [])) {
|
|
1195
1215
|
console.log(`\n\n## Run ${run.run_number} (${run.id})`)
|
|
1196
1216
|
console.log(`started: ${run.started_at}${run.finished_at ? ` finished: ${run.finished_at}` : ' (in progress)'}`)
|
package/package.json
CHANGED