@1aboveio/skills 0.20.3 → 0.21.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/README.md +14 -9
- package/package.json +1 -1
- package/runtime/skills/distribution/generated/recipes.json +136 -24
- package/runtime/skills/distribution/scripts/bundles.mjs +152 -76
- package/runtime/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
- package/skills/compliance/doc-authenticity/SKILL.md +2 -1
- package/skills/compliance/doc-authenticity/agents/openai.yaml +8 -0
- package/skills/content-creator/article-review/LICENSE +3 -0
- package/skills/content-creator/article-review/SKILL.md +65 -0
- package/skills/content-creator/article-review/references/report-format.md +68 -0
- package/skills/content-creator/article-review/references/template-guide.md +79 -0
- package/skills/content-creator/article-review/references/template-wechat-payments.md +125 -0
- package/skills/content-creator/article-to-image/LICENSE +3 -0
- package/skills/content-creator/article-to-image/SKILL.md +90 -0
- package/skills/content-creator/article-to-image/references/formats.md +53 -0
- package/skills/content-creator/wechat-article-writer/LICENSE +3 -0
- package/skills/content-creator/wechat-article-writer/SKILL.md +50 -0
- package/skills/content-creator/wechat-article-writer/references/style-comparison.md +64 -0
- package/skills/content-creator/wechat-article-writer/references/style-howto.md +64 -0
- package/skills/content-creator/wechat-article-writer/references/style-insight.md +63 -0
- package/skills/content-creator/wechat-article-writer/references/style-pitfall.md +60 -0
- package/skills/content-creator/wechat-article-writer/references/style-selector.md +133 -0
- package/skills/content-creator/wechat-article-writer/references/writing-rules.md +150 -0
- package/skills/content-creator/wechat-publisher/LICENSE +3 -0
- package/skills/content-creator/wechat-publisher/SKILL.md +170 -0
- package/skills/content-creator/wechat-publisher/references/api.md +105 -0
- package/skills/content-creator/wechat-publisher/references/layout-rules.md +57 -0
- package/skills/content-creator/wechat-publisher/references/wechat-safe-template.html +86 -0
- package/skills/content-creator/wechat-publisher/scripts/md_to_wechat_html.py +194 -0
- package/skills/content-creator/wechat-publisher/scripts/publish.py +239 -0
- package/skills/content-creator/wechat-search/LICENSE +3 -0
- package/skills/content-creator/wechat-search/SKILL.md +210 -0
- package/skills/content-creator/wechat-search/scripts/start-chrome.sh +83 -0
- package/skills/engineering/engineering-runtime/coherence/workflow.json +13 -13
- package/skills/engineering/engineering-runtime/scripts/workflow-policy.mjs +1 -1
|
@@ -204,18 +204,22 @@ export function newMemberCandidates(action, groupIds, priorState, transport = {}
|
|
|
204
204
|
return candidates;
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
// Archived members are offered
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
// later
|
|
207
|
+
// Archived members of a selected group are offered when they were previously accepted or are
|
|
208
|
+
// still installed. Presence of a named leftover is not group inference: the group is already
|
|
209
|
+
// selected. A successful removal drops the old acceptance; a decline or failure preserves it so a
|
|
210
|
+
// later run offers the archive again.
|
|
211
211
|
export function archivedMemberCandidates(action, groupIds, priorState, transport = {}) {
|
|
212
|
-
if (action
|
|
212
|
+
if (!ACTIONS.has(action)) return [];
|
|
213
213
|
const recipes = readRecipes(resolveBundleTransport(transport).recipesPath);
|
|
214
214
|
const selected = new Set(groupIds);
|
|
215
|
-
const acceptedByGroup = new Map(priorState
|
|
215
|
+
const acceptedByGroup = new Map((priorState?.groups ?? [])
|
|
216
216
|
.map((group) => [group.id, new Set(group.acceptedMembers)]));
|
|
217
|
+
const leftover = (name) => Boolean(
|
|
218
|
+
transport.skillsRoot
|
|
219
|
+
&& statSync(join(transport.skillsRoot, name), { throwIfNoEntry: false }),
|
|
220
|
+
);
|
|
217
221
|
return (recipes.archives ?? []).filter((archive) => selected.has(archive.formerGroupId)
|
|
218
|
-
&& acceptedByGroup.get(archive.formerGroupId)?.has(archive.installName));
|
|
222
|
+
&& (acceptedByGroup.get(archive.formerGroupId)?.has(archive.installName) || leftover(archive.installName)));
|
|
219
223
|
}
|
|
220
224
|
|
|
221
225
|
// Prior acceptance for the new-member consent delta on explicit selections (picker, --group,
|
|
@@ -305,19 +309,14 @@ function localFirstPartyCommand(command, localRoot, groupId) {
|
|
|
305
309
|
return command.replace(NATIVE_ADD_SOURCE, (_match, prefix) => `${prefix}${shellArgument(localRoot)}`);
|
|
306
310
|
}
|
|
307
311
|
|
|
308
|
-
// The coordinator
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
// planned exactly once, at its first occurrence. The pinned native CLI has no `--symlink` selector,
|
|
314
|
-
// so the coordinator adds `--yes` to choose its default symlink deployment without one installation-
|
|
315
|
-
// method prompt per skill. Attribution, ordering and deduplication remain coordinator-owned.
|
|
316
|
-
const SKILL_TAIL = /^(.*? --skill )(.+?)( && .*)?$/;
|
|
312
|
+
// The coordinator preserves generated dependency/member order, then coalesces each contiguous run
|
|
313
|
+
// from one source into a native add. A member shared by groups is included once and retains every
|
|
314
|
+
// owning selection for memory. Per-member attribution comes from post-install digest verification,
|
|
315
|
+
// not process count.
|
|
316
|
+
const SKILL_TAIL = /^(.*? --skill )(.+?)( --yes)?( && .*)?$/;
|
|
317
317
|
|
|
318
|
-
// The generated step binds its runtime suffix (` && npm ci ...`) to
|
|
319
|
-
//
|
|
320
|
-
// engineering-runtime itself was actually added — never after a sibling, never when it was skipped.
|
|
318
|
+
// The generated step binds its runtime suffix (` && npm ci ...`) to engineering-runtime. Keep it on
|
|
319
|
+
// the source batch only when that member remains selected after digest and consent filtering.
|
|
321
320
|
const RUNTIME_MEMBER = 'engineering-runtime';
|
|
322
321
|
|
|
323
322
|
function skillsFromCommand(command) {
|
|
@@ -329,14 +328,14 @@ function skillsFromCommand(command) {
|
|
|
329
328
|
function withSkills(command, skills) {
|
|
330
329
|
const match = SKILL_TAIL.exec(command);
|
|
331
330
|
if (!match) throw new Error(`Native add has no --skill list to rewrite: ${command}`);
|
|
332
|
-
return `${match[1]}${skills.join(' ')}${match[3] || ''}`;
|
|
331
|
+
return `${match[1]}${skills.join(' ')}${match[3] || ''}${match[4] || ''}`;
|
|
333
332
|
}
|
|
334
333
|
|
|
335
334
|
function withYesFlag(command) {
|
|
336
335
|
// --yes must bind to `skills`, not to a trailing `&& npm ci ...`.
|
|
337
336
|
const match = SKILL_TAIL.exec(command);
|
|
338
337
|
if (!match) return `${command} --yes`;
|
|
339
|
-
return `${match[1]}${match[2]} --yes${match[
|
|
338
|
+
return `${match[1]}${match[2]} --yes${match[4] || ''}`;
|
|
340
339
|
}
|
|
341
340
|
|
|
342
341
|
export function planBundleCommands(action, groupIds, {
|
|
@@ -363,12 +362,10 @@ export function planBundleCommands(action, groupIds, {
|
|
|
363
362
|
if (!requested.includes(groupId)) requested.push(groupId);
|
|
364
363
|
}
|
|
365
364
|
|
|
366
|
-
// Plan
|
|
367
|
-
//
|
|
368
|
-
// would install skills that are not in the selection / not in the package.
|
|
365
|
+
// Plan individual member records first. The execution boundary coalesces adjacent records from
|
|
366
|
+
// the same source without weakening per-member dependency, policy, or selection metadata.
|
|
369
367
|
const planned = [];
|
|
370
368
|
const plannedByMember = new Map();
|
|
371
|
-
|
|
372
369
|
for (const groupId of requested) {
|
|
373
370
|
const group = groups.get(groupId);
|
|
374
371
|
for (const step of group[action].steps) {
|
|
@@ -379,14 +376,10 @@ export function planBundleCommands(action, groupIds, {
|
|
|
379
376
|
const command = step.sourceId === 'first-party'
|
|
380
377
|
? localFirstPartyCommand(generated.command, localRoot, groupId)
|
|
381
378
|
: generated.command;
|
|
382
|
-
// An explicit member subset (the digest skip) removes members from the native add but never
|
|
383
|
-
// reorders the rest: the remaining skills keep recipe order, support dependency first.
|
|
384
|
-
// Declined new members (#1128) are also filtered out, but consent never applies to support
|
|
385
|
-
// dependencies (harness-runtime): they are silent install dependencies, not capabilities.
|
|
386
379
|
const skills = skillsFromCommand(command)
|
|
387
380
|
.filter((skill) => !skipMembers?.has(skill))
|
|
388
381
|
.filter((skill) => SUPPORT_GROUPS.has(skill) || !omit.has(skill));
|
|
389
|
-
const suffix = SKILL_TAIL.exec(command)?.[
|
|
382
|
+
const suffix = SKILL_TAIL.exec(command)?.[4] || '';
|
|
390
383
|
const bare = suffix ? command.slice(0, command.length - suffix.length) : command;
|
|
391
384
|
for (const skill of skills) {
|
|
392
385
|
const existing = plannedByMember.get(skill);
|
|
@@ -396,8 +389,6 @@ export function planBundleCommands(action, groupIds, {
|
|
|
396
389
|
}
|
|
397
390
|
let skillCommand = withSkills(bare, [skill]);
|
|
398
391
|
if (suffix && skill === RUNTIME_MEMBER) skillCommand = `${skillCommand}${suffix}`;
|
|
399
|
-
// The pinned CLI has no --symlink flag. --yes is its only supported way to select the
|
|
400
|
-
// default symlink deployment without presenting the install-method chooser.
|
|
401
392
|
skillCommand = withYesFlag(skillCommand);
|
|
402
393
|
const invocationPolicy = skill === 'tdd' && enableTdd
|
|
403
394
|
? undefined
|
|
@@ -415,22 +406,51 @@ export function planBundleCommands(action, groupIds, {
|
|
|
415
406
|
...(memberMetadata.get(skill)?.requires?.length > 0
|
|
416
407
|
? { requires: memberMetadata.get(skill).requires }
|
|
417
408
|
: {}),
|
|
409
|
+
metadata: memberMetadata.get(skill) ?? { installName: skill },
|
|
418
410
|
};
|
|
419
411
|
plannedByMember.set(skill, entry);
|
|
420
412
|
planned.push(entry);
|
|
421
413
|
}
|
|
422
414
|
}
|
|
423
415
|
}
|
|
424
|
-
|
|
425
416
|
return planned;
|
|
426
417
|
}
|
|
427
418
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
419
|
+
export function coalesceBundleCommands(commands) {
|
|
420
|
+
const batches = [];
|
|
421
|
+
for (const planned of commands) {
|
|
422
|
+
const previous = batches.at(-1);
|
|
423
|
+
if (!previous || previous.sourceId !== planned.sourceId || previous.cwd !== planned.cwd) {
|
|
424
|
+
batches.push({
|
|
425
|
+
sourceId: planned.sourceId,
|
|
426
|
+
members: [planned],
|
|
427
|
+
command: planned.command,
|
|
428
|
+
cwd: planned.cwd,
|
|
429
|
+
failureMessage: planned.failureMessage,
|
|
430
|
+
});
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
previous.members.push(planned);
|
|
434
|
+
const suffix = previous.members.map(({ command }) => SKILL_TAIL.exec(command)?.[4] || '').find(Boolean) || '';
|
|
435
|
+
const bare = suffix && previous.command.endsWith(suffix)
|
|
436
|
+
? previous.command.slice(0, previous.command.length - suffix.length)
|
|
437
|
+
: previous.command;
|
|
438
|
+
previous.command = withSkills(bare, previous.members.map(({ member }) => member));
|
|
439
|
+
if (suffix) previous.command = `${previous.command}${suffix}`;
|
|
440
|
+
}
|
|
441
|
+
for (const batch of batches) {
|
|
442
|
+
batch.groupIds = [...new Set(batch.members.flatMap((member) => member.groupIds))];
|
|
443
|
+
batch.groupName = [...new Set(batch.members.map((member) => member.groupName))].join(', ');
|
|
444
|
+
}
|
|
445
|
+
return batches;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Source batches continue independently. Within each batch, command status is only process evidence;
|
|
449
|
+
// installed-content verification is the per-member authority used for attribution and memory.
|
|
450
|
+
export function runBundleCommands(batches, {
|
|
432
451
|
execute = (command, cwd) => spawnSync('/bin/bash', ['-lc', command], { cwd, stdio: 'inherit' }),
|
|
433
452
|
applyPolicy = applyInvocationPolicy,
|
|
453
|
+
verifyMember,
|
|
434
454
|
skillsRoot,
|
|
435
455
|
stdout = process.stdout,
|
|
436
456
|
stderr = process.stderr,
|
|
@@ -439,35 +459,79 @@ export function runBundleCommands(commands, {
|
|
|
439
459
|
const succeeded = [];
|
|
440
460
|
const failed = [];
|
|
441
461
|
const available = new Set(satisfiedMembers);
|
|
442
|
-
for (const
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
462
|
+
for (const candidate of batches) {
|
|
463
|
+
const legacySingle = !Array.isArray(candidate.members);
|
|
464
|
+
const batch = legacySingle ? { ...candidate, members: [candidate] } : candidate;
|
|
465
|
+
let runnable = [...batch.members];
|
|
466
|
+
let changed = true;
|
|
467
|
+
while (changed) {
|
|
468
|
+
changed = false;
|
|
469
|
+
const runnableNames = new Set(runnable.map(({ member }) => member));
|
|
470
|
+
for (const planned of [...runnable]) {
|
|
471
|
+
const blockedBy = (planned.requires ?? [])
|
|
472
|
+
.filter((dependency) => !available.has(dependency) && !runnableNames.has(dependency));
|
|
473
|
+
if (blockedBy.length === 0) continue;
|
|
474
|
+
stderr.write(`Dependency failed for ${planned.member}: ${blockedBy.join(', ')} is not available\n`);
|
|
475
|
+
failed.push({ planned, status: 1, blockedBy });
|
|
476
|
+
runnable = runnable.filter((candidate) => candidate !== planned);
|
|
477
|
+
changed = true;
|
|
478
|
+
}
|
|
448
479
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
480
|
+
if (runnable.length === 0) continue;
|
|
481
|
+
let command = batch.command;
|
|
482
|
+
let batchSuffix = '';
|
|
483
|
+
if (!legacySingle) {
|
|
484
|
+
batchSuffix = SKILL_TAIL.exec(batch.command)?.[4] || '';
|
|
485
|
+
command = withSkills(batch.command, runnable.map(({ member }) => member));
|
|
486
|
+
if (batchSuffix && !runnable.some(({ member }) => member === RUNTIME_MEMBER)) {
|
|
487
|
+
command = command.slice(0, command.length - batchSuffix.length);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
stdout.write(`\nInstalling ${runnable.map(({ member }) => member).join(', ')} (${batch.sourceId})...\n`);
|
|
491
|
+
const result = execute(command, batch.cwd);
|
|
492
|
+
const status = Number.isInteger(result.status) ? result.status : 1;
|
|
493
|
+
const policyFailures = new Set();
|
|
494
|
+
if (status === 0) {
|
|
495
|
+
for (const planned of runnable) {
|
|
496
|
+
if (!planned.invocationPolicy) continue;
|
|
497
|
+
try {
|
|
454
498
|
applyPolicy({
|
|
455
499
|
installName: planned.member,
|
|
456
500
|
policy: planned.invocationPolicy,
|
|
457
501
|
...(skillsRoot ? { skillsRoot } : {}),
|
|
458
502
|
});
|
|
503
|
+
} catch (error) {
|
|
504
|
+
stderr.write(`Invocation policy failed for ${planned.member}: ${error.message}\n`);
|
|
505
|
+
policyFailures.add(planned.member);
|
|
459
506
|
}
|
|
460
|
-
|
|
461
|
-
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
stderr.write(`${batch.failureMessage}\n`);
|
|
510
|
+
}
|
|
511
|
+
for (const planned of runnable) {
|
|
512
|
+
if (policyFailures.has(planned.member)) {
|
|
513
|
+
failed.push({ planned, status: 1 });
|
|
462
514
|
continue;
|
|
515
|
+
}
|
|
516
|
+
if (status !== 0 && batchSuffix && planned.member === RUNTIME_MEMBER) {
|
|
517
|
+
stderr.write(`Post-install command failed for ${planned.member}\n`);
|
|
518
|
+
failed.push({ planned, status });
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
try {
|
|
522
|
+
const verified = verifyMember ? verifyMember(planned) : status === 0;
|
|
523
|
+
if (verified) {
|
|
524
|
+
succeeded.push(planned);
|
|
525
|
+
available.add(planned.member);
|
|
526
|
+
} else {
|
|
527
|
+
stderr.write(`Post-install verification failed for ${planned.member}\n`);
|
|
528
|
+
failed.push({ planned, status: status || 1 });
|
|
529
|
+
}
|
|
463
530
|
} catch (error) {
|
|
464
|
-
stderr.write(`
|
|
531
|
+
stderr.write(`Post-install verification failed for ${planned.member}: ${error.message}\n`);
|
|
465
532
|
failed.push({ planned, status: 1 });
|
|
466
|
-
continue;
|
|
467
533
|
}
|
|
468
534
|
}
|
|
469
|
-
stderr.write(`${planned.failureMessage}\n`);
|
|
470
|
-
failed.push({ planned, status: Number.isInteger(result.status) ? result.status : 1 });
|
|
471
535
|
}
|
|
472
536
|
return { succeeded, failed };
|
|
473
537
|
}
|
|
@@ -626,8 +690,8 @@ install without --group/--all opens the interactive picker; update without them
|
|
|
626
690
|
remembered selection (\${XDG_STATE_HOME:-~/.local/state}/1aboveio-skills/selection.json).
|
|
627
691
|
When a remembered group's current recipe declares members you have not accepted yet, each new skill
|
|
628
692
|
is offered individually and defaults to No; --yes accepts them all, --dry-run lists them without
|
|
629
|
-
prompting. Retired
|
|
630
|
-
|
|
693
|
+
prompting. Retired leftovers in a selected group are archived by default through the native Skills
|
|
694
|
+
CLI after their replacement is current; decline interactively to keep them. TDD implicit model invocation is enabled by default; --disable-tdd makes TDD explicit-only,
|
|
631
695
|
and --enable-tdd restores implicit invocation. --manual-tdd remains a compatibility alias for
|
|
632
696
|
--disable-tdd.
|
|
633
697
|
`;
|
|
@@ -802,29 +866,24 @@ async function consentNewMembers(candidates, { yes, stdin, stdout, stderr }) {
|
|
|
802
866
|
return declined;
|
|
803
867
|
}
|
|
804
868
|
|
|
805
|
-
async function consentArchivedMembers(candidates, { yes, stdin, stdout
|
|
869
|
+
async function consentArchivedMembers(candidates, { yes, stdin, stdout }) {
|
|
806
870
|
const declined = new Set();
|
|
807
|
-
if (yes) {
|
|
808
|
-
|
|
809
|
-
return declined;
|
|
810
|
-
}
|
|
811
|
-
if (!stdin.isTTY) {
|
|
871
|
+
if (yes || !stdin.isTTY) {
|
|
872
|
+
const how = yes ? '--yes' : 'default Yes';
|
|
812
873
|
for (const candidate of candidates) {
|
|
813
|
-
|
|
814
|
-
declined.add(candidate.installName);
|
|
874
|
+
stdout.write(`Archiving retired skill "${candidate.installName}" (${how}; replaced by ${candidate.replacement})\n`);
|
|
815
875
|
}
|
|
816
876
|
return declined;
|
|
817
877
|
}
|
|
818
878
|
const prompter = createLinePrompter({ stdin, stdout });
|
|
819
879
|
for (const candidate of candidates) {
|
|
820
880
|
const answer = await prompter.ask(
|
|
821
|
-
`Archive retired skill "${candidate.installName}" (replaced by ${candidate.replacement})? [
|
|
881
|
+
`Archive retired skill "${candidate.installName}" (replaced by ${candidate.replacement})? [Y/n] `,
|
|
822
882
|
);
|
|
823
|
-
if (/^
|
|
824
|
-
else {
|
|
883
|
+
if (/^n(o)?$/i.test(answer)) {
|
|
825
884
|
stdout.write(`Keeping ${candidate.installName}\n`);
|
|
826
885
|
declined.add(candidate.installName);
|
|
827
|
-
}
|
|
886
|
+
} else stdout.write(`Archiving ${candidate.installName}\n`);
|
|
828
887
|
}
|
|
829
888
|
return declined;
|
|
830
889
|
}
|
|
@@ -873,6 +932,7 @@ function selectionIncludesTdd(groupIds, catalog) {
|
|
|
873
932
|
export async function runCli(args, {
|
|
874
933
|
execute,
|
|
875
934
|
applyPolicy,
|
|
935
|
+
verifyMember,
|
|
876
936
|
stdin = process.stdin,
|
|
877
937
|
stdout = process.stdout,
|
|
878
938
|
stderr = process.stderr,
|
|
@@ -932,18 +992,21 @@ export async function runCli(args, {
|
|
|
932
992
|
} else if (candidates.length > 0) {
|
|
933
993
|
declined = await consentNewMembers(candidates, { yes: parsed.yes, stdin, stdout, stderr });
|
|
934
994
|
}
|
|
935
|
-
const
|
|
995
|
+
const skillsRoot = join(env.HOME || homedir(), '.agents', 'skills');
|
|
996
|
+
const archiveCandidates = archivedMemberCandidates(parsed.action, groupIds, priorState, {
|
|
997
|
+
...transport,
|
|
998
|
+
skillsRoot,
|
|
999
|
+
});
|
|
936
1000
|
let declinedArchives = new Set();
|
|
937
1001
|
if (archiveCandidates.length > 0 && parsed.dryRun) {
|
|
938
1002
|
for (const candidate of archiveCandidates) {
|
|
939
|
-
stdout.write(`Retired skill "${candidate.installName}" (replaced by ${candidate.replacement}) — would
|
|
1003
|
+
stdout.write(`Retired skill "${candidate.installName}" (replaced by ${candidate.replacement}) — would archive by default; decline interactively to keep\n`);
|
|
940
1004
|
}
|
|
941
1005
|
} else if (archiveCandidates.length > 0) {
|
|
942
1006
|
declinedArchives = await consentArchivedMembers(archiveCandidates, {
|
|
943
1007
|
yes: parsed.yes,
|
|
944
1008
|
stdin,
|
|
945
1009
|
stdout,
|
|
946
|
-
stderr,
|
|
947
1010
|
});
|
|
948
1011
|
}
|
|
949
1012
|
// Digest skip (#1127): `update` classifies the accepted members against the installed tree and
|
|
@@ -952,7 +1015,6 @@ export async function runCli(args, {
|
|
|
952
1015
|
// from classification: they never run, so they are not buckets.
|
|
953
1016
|
let buckets;
|
|
954
1017
|
let skipMembers;
|
|
955
|
-
const skillsRoot = join(env.HOME || homedir(), '.agents', 'skills');
|
|
956
1018
|
if (parsed.action === 'update') {
|
|
957
1019
|
buckets = classifySelectedMembers(parsed.action, groupIds, { ...transport, enableTdd, skillsRoot });
|
|
958
1020
|
skipMembers = new Set(buckets.unchanged.map((entry) => entry.member));
|
|
@@ -968,10 +1030,11 @@ export async function runCli(args, {
|
|
|
968
1030
|
skipMembers,
|
|
969
1031
|
omitMembers: declined,
|
|
970
1032
|
});
|
|
1033
|
+
const batches = coalesceBundleCommands(commands);
|
|
971
1034
|
if (parsed.dryRun) {
|
|
972
1035
|
if (buckets) printUpdateBuckets(stdout, buckets);
|
|
973
|
-
for (const
|
|
974
|
-
stdout.write(`\n${
|
|
1036
|
+
for (const batch of batches) {
|
|
1037
|
+
stdout.write(`\n${batch.groupName} / ${batch.sourceId} / ${batch.members.map(({ member }) => member).join(', ')}\n${batch.command}\n`);
|
|
975
1038
|
}
|
|
976
1039
|
if (buckets && commands.length === 0 && archiveCandidates.length === 0) {
|
|
977
1040
|
stdout.write('Nothing to do: every accepted skill is already up to date.\n');
|
|
@@ -984,9 +1047,21 @@ export async function runCli(args, {
|
|
|
984
1047
|
const skipping = buckets.unchanged.map((entry) => entry.member).filter((member) => !SUPPORT_GROUPS.has(member));
|
|
985
1048
|
if (skipping.length > 0) stdout.write(`Up to date, skipping: ${skipping.join(', ')}\n`);
|
|
986
1049
|
}
|
|
987
|
-
const
|
|
1050
|
+
const verificationTransport = resolveBundleTransport(transport);
|
|
1051
|
+
const verificationSources = new Map(readRecipes(verificationTransport.recipesPath).sources
|
|
1052
|
+
.map((source) => [source.id, source]));
|
|
1053
|
+
const verifyInstalledMember = verifyMember ?? (execute ? undefined : (planned) => {
|
|
1054
|
+
const source = verificationSources.get(planned.sourceId);
|
|
1055
|
+
return classifyInstalledMember(source, planned.metadata, {
|
|
1056
|
+
enableTdd,
|
|
1057
|
+
localRoot: verificationTransport.localRoot,
|
|
1058
|
+
skillsRoot,
|
|
1059
|
+
}).status === 'unchanged';
|
|
1060
|
+
});
|
|
1061
|
+
const { succeeded, failed } = runBundleCommands(batches, {
|
|
988
1062
|
execute,
|
|
989
1063
|
applyPolicy,
|
|
1064
|
+
verifyMember: verifyInstalledMember,
|
|
990
1065
|
skillsRoot,
|
|
991
1066
|
stdout,
|
|
992
1067
|
stderr,
|
|
@@ -1001,13 +1076,14 @@ export async function runCli(args, {
|
|
|
1001
1076
|
[...commands, ...(buckets?.unchanged ?? [])],
|
|
1002
1077
|
stderr,
|
|
1003
1078
|
));
|
|
1079
|
+
const coherentSucceeded = succeeded.filter((planned) => readyMembers.has(planned.member));
|
|
1004
1080
|
const archives = runArchiveCommands(
|
|
1005
1081
|
archiveCandidates.filter((candidate) => !declinedArchives.has(candidate.installName)),
|
|
1006
1082
|
readyMembers,
|
|
1007
1083
|
{ execute, skillsRoot, stdout, stderr },
|
|
1008
1084
|
);
|
|
1009
1085
|
printRunSummary(stdout, {
|
|
1010
|
-
succeeded,
|
|
1086
|
+
succeeded: coherentSucceeded,
|
|
1011
1087
|
failed,
|
|
1012
1088
|
unchanged: buckets?.unchanged.map((entry) => entry.member),
|
|
1013
1089
|
declined: [...declined],
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: doc-authenticity
|
|
3
|
-
version: 0.4.
|
|
3
|
+
version: 0.4.1
|
|
4
4
|
description: "Screen PDF documents and raster images for fabrication, tampering, Photoshop or other image-editor use, and content validity. Detects file type, runs format-specific authenticity forensics, classifies bank statements, conversation logs, invoices, agreements, and other documents, then applies type-specific validity checks. Produces separate authenticity (NO_EVIDENCE_OF_MODIFICATION / SUSPICIOUS / LIKELY_MODIFIED / INCONCLUSIVE) and validity (VALID / INVALID / INCONCLUSIVE) verdicts. Use whenever a user asks whether a document, screenshot, scan, statement, invoice, contract, chat log, receipt, or payment evidence is genuine, original, edited, Photoshopped, manipulated, fake, or valid."
|
|
5
|
+
disable-model-invocation: true
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Document and Image Authenticity
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface:
|
|
2
|
+
display_name: "Document Authenticity"
|
|
3
|
+
short_description: "Explicit-only document and image authenticity screening"
|
|
4
|
+
|
|
5
|
+
policy:
|
|
6
|
+
# Codex counterpart to SKILL.md disable-model-invocation: true.
|
|
7
|
+
# Keeps explicit invocation while blocking description-based selection.
|
|
8
|
+
allow_implicit_invocation: false
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: article-review
|
|
3
|
+
description: >
|
|
4
|
+
Evaluate and score articles across multiple dimensions including originality,
|
|
5
|
+
readability, shareability, timeliness, and factual accuracy.
|
|
6
|
+
Use when a user shares an article, pastes article text, sends a URL, or asks to
|
|
7
|
+
review, rate, critique, evaluate, 评价, 点评, 分析, or 看看 any article, post, or 文章.
|
|
8
|
+
Default template is 公众号-支付行业.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# 文章评审引擎
|
|
12
|
+
|
|
13
|
+
模板化的文章评审系统。评审标准由模板文件定义,引擎负责执行评审流程并输出标准化报告。
|
|
14
|
+
|
|
15
|
+
**开始任何评审前,先读取 `references/report-format.md`**,熟悉输出结构。
|
|
16
|
+
|
|
17
|
+
## 可用模板
|
|
18
|
+
|
|
19
|
+
- **公众号-支付行业** (`references/template-wechat-payments.md`) — 面向支付/金融科技从业者的公众号内容(默认)
|
|
20
|
+
|
|
21
|
+
如需其他模板,或用户请求的模板不存在,读取 `references/template-guide.md` 创建新模板,再继续评审。
|
|
22
|
+
|
|
23
|
+
## 输入处理
|
|
24
|
+
|
|
25
|
+
**粘贴文字:** 直接进入评审流程。
|
|
26
|
+
|
|
27
|
+
**URL:** 用 `web_fetch` 抓取文章正文。若被拦截(如微信需要登录),提示用户粘贴文章内容后继续。
|
|
28
|
+
|
|
29
|
+
## 评审流程
|
|
30
|
+
|
|
31
|
+
### 第一步:确认模板
|
|
32
|
+
在每次评审开始时,读取对应模板文件(默认 `references/template-wechat-payments.md`),获取维度列表和 Level 2 细则。用户未指定模板时使用默认。
|
|
33
|
+
|
|
34
|
+
### 第二步:通读文章
|
|
35
|
+
通读全文,理解核心主张、论据、结构、受众定位。
|
|
36
|
+
|
|
37
|
+
### 第三步:事实核查(Fact-Check Pass)
|
|
38
|
+
在逐维度评分之前,先做事实核查:
|
|
39
|
+
|
|
40
|
+
1. 提取文章中所有可验证的主张:
|
|
41
|
+
- 数字、统计、比例、排名
|
|
42
|
+
- 引用他人言论或研究结论
|
|
43
|
+
- 行业事实陈述(市场规模、政策、技术参数等)
|
|
44
|
+
|
|
45
|
+
2. 对每条主张用 `web_search` 核查,标注结果:
|
|
46
|
+
- ✅ **已核实** — 找到一致的可信来源
|
|
47
|
+
- ❌ **有误** — 找到与之矛盾的可信来源,记录正确值
|
|
48
|
+
- ⚠️ **无法核实** — 无法找到来源或来源相互矛盾,标注存疑
|
|
49
|
+
|
|
50
|
+
3. 将核查结果用于事实准确性维度的评分依据
|
|
51
|
+
|
|
52
|
+
### 第四步:逐维度评分
|
|
53
|
+
按模板定义的每个 Level 1 维度:
|
|
54
|
+
- 给出 1-10 分
|
|
55
|
+
- 引用文章原文 2-3 处作为依据
|
|
56
|
+
- 给出 1-2 条具体可执行的改进建议(如有)
|
|
57
|
+
|
|
58
|
+
事实准确性维度的评分必须基于第三步的核查结果,而非主观判断。
|
|
59
|
+
|
|
60
|
+
### 第五步:输出报告
|
|
61
|
+
按 `references/report-format.md` 中的模板输出完整报告。
|
|
62
|
+
|
|
63
|
+
## 注意事项
|
|
64
|
+
|
|
65
|
+
严格按流程执行:评分必须有原文引用,不接受笼统判断;事实准确性必须基于 web_search 核查;改进建议要具体可执行;发布建议根据综合分和事实核查结果综合判断。
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# 评审报告输出格式
|
|
2
|
+
|
|
3
|
+
使用以下模板输出完整评审报告。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
# 文章评审报告
|
|
9
|
+
|
|
10
|
+
**文章标题:** [标题]
|
|
11
|
+
**评审模板:** [模板名称]
|
|
12
|
+
**评审时间:** [日期]
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 综合评分:X.X / 10
|
|
17
|
+
(各维度等权平均)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 事实核查结果
|
|
22
|
+
|
|
23
|
+
- ✅ "[原文陈述]" — 来源:[来源名]
|
|
24
|
+
- ❌ "[原文陈述]" — 实际应为:[正确值],来源:[来源名]
|
|
25
|
+
- ⚠️ "[原文陈述]" — 无法核实,建议补充来源
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 详细评分
|
|
30
|
+
|
|
31
|
+
### [维度名] X/10
|
|
32
|
+
**依据:**
|
|
33
|
+
- [引用原文 + 分析]
|
|
34
|
+
|
|
35
|
+
**改进建议:**
|
|
36
|
+
- [具体建议]
|
|
37
|
+
|
|
38
|
+
[其余维度同上格式...]
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 传播潜力分析
|
|
43
|
+
|
|
44
|
+
**最强传播钩子:**
|
|
45
|
+
> "[从文章中提取或改写的最具传播力的 1-2 句话,可直接用作转发文案]"
|
|
46
|
+
|
|
47
|
+
**建议传播渠道:**
|
|
48
|
+
- 微信群(行业/专业群):[适合/不适合,理由一句]
|
|
49
|
+
- 朋友圈:[适合/不适合,理由一句]
|
|
50
|
+
- LinkedIn:[适合/不适合,理由一句]
|
|
51
|
+
|
|
52
|
+
**传播半径预估:**
|
|
53
|
+
- [ ] 圈内传播(支付/金融科技从业者为主)
|
|
54
|
+
- [ ] 破圈传播(有潜力触达行业外受众)
|
|
55
|
+
|
|
56
|
+
**提升传播力的快速改动(可选):**
|
|
57
|
+
- [1-2 条具体改动建议]
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 总结
|
|
62
|
+
[2-3 句总体评价,突出最大优势和最需改进的地方]
|
|
63
|
+
|
|
64
|
+
## 发布建议
|
|
65
|
+
[ ] 可直接发布
|
|
66
|
+
[ ] 修改后发布(需解决上述改进点)
|
|
67
|
+
[ ] 建议重写(存在严重问题)
|
|
68
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# 新建评审模板指南
|
|
2
|
+
|
|
3
|
+
新建模板时,复制以下结构,保存为 `references/template-<name>.md`,
|
|
4
|
+
然后在 `SKILL.md` 的"可用模板"列表中添加一行。
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 模板文件结构
|
|
9
|
+
|
|
10
|
+
```markdown
|
|
11
|
+
# 评审模板:[模板名称]
|
|
12
|
+
|
|
13
|
+
**适用范围:** [描述适用的文章类型]
|
|
14
|
+
**目标受众:** [描述文章面向的读者群体]
|
|
15
|
+
**模板版本:** v1.0
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 评审维度(Level 1)及细则(Level 2)
|
|
20
|
+
|
|
21
|
+
### 1. [维度名] [英文名]
|
|
22
|
+
|
|
23
|
+
[维度说明,1-2句]
|
|
24
|
+
|
|
25
|
+
**Level 2 细则:**
|
|
26
|
+
- **[细则名]**:[说明]
|
|
27
|
+
- **[细则名]**:[说明]
|
|
28
|
+
- (2-5条)
|
|
29
|
+
|
|
30
|
+
**评分参考:**
|
|
31
|
+
- 9-10:[优秀标准]
|
|
32
|
+
- 6-8:[良好标准]
|
|
33
|
+
- 3-5:[及格标准]
|
|
34
|
+
- 1-2:[不合格标准]
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### 2. [下一个维度...]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 设计原则
|
|
44
|
+
|
|
45
|
+
**维度数量:** 建议 4-6 个 Level 1 维度。维度太多会分散注意力;太少则覆盖不全。
|
|
46
|
+
|
|
47
|
+
**维度选择:** 每个维度应该:
|
|
48
|
+
- 相互独立(不重叠)
|
|
49
|
+
- 合起来覆盖文章质量的核心方面
|
|
50
|
+
- 与受众和文章类型紧密相关
|
|
51
|
+
|
|
52
|
+
**事实准确性注意:** 若模板包含"事实准确性"维度,必须包含以下说明:
|
|
53
|
+
> ⚠️ 本维度评分必须基于 web_search 实际核查结果,不接受主观判断。
|
|
54
|
+
|
|
55
|
+
**Level 2 细则:** 每个维度建议 3-5 条细则,每条清晰说明考察什么。
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 模板示例说明
|
|
60
|
+
|
|
61
|
+
| 场景 | 文件名建议 | 差异化维度 |
|
|
62
|
+
|------|-----------|-----------|
|
|
63
|
+
| 面向 B 端 CFO 的行业报告 | `template-cfo-report.md` | 数据严谨性、决策价值 |
|
|
64
|
+
| 面向年轻用户的品牌故事 | `template-brand-story.md` | 情感温度、品牌一致性 |
|
|
65
|
+
| 合规/政策解读类 | `template-policy.md` | 准确性权重更高、实操指导 |
|
|
66
|
+
| 产品功能介绍 | `template-product.md` | 清晰度、用例覆盖、CTA |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 注册新模板
|
|
71
|
+
|
|
72
|
+
完成模板文件后,更新 `SKILL.md` 中的"可用模板"列表:
|
|
73
|
+
|
|
74
|
+
```markdown
|
|
75
|
+
## 可用模板
|
|
76
|
+
|
|
77
|
+
- **通用** (`references/template-general.md`) — 适用于大多数 [品牌] 公众号内容(默认)
|
|
78
|
+
- **[新模板名]** (`references/template-<name>.md`) — [一句话描述]
|
|
79
|
+
```
|