@ai-content-space/loopx 0.1.9 → 0.2.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/AGENTS.md +50 -0
- package/README.md +59 -446
- package/README.zh-CN.md +59 -457
- package/docs/loopx/design/loopx-skill-suite-v1-design.md +73 -0
- package/docs/loopx/plans/loopx-skill-suite-v1-implementation.md +77 -0
- package/package.json +5 -2
- package/plugins/loopx/.codex-plugin/plugin.json +4 -4
- package/plugins/loopx/scripts/plugin-install.test.mjs +20 -20
- package/plugins/loopx/skills/clarify/SKILL.md +38 -311
- package/plugins/loopx/skills/debug/SKILL.md +1 -1
- package/plugins/loopx/skills/exec/SKILL.md +71 -0
- package/plugins/loopx/skills/finish/SKILL.md +254 -0
- package/plugins/loopx/skills/fix-review/SKILL.md +216 -0
- package/plugins/loopx/skills/go-style/SKILL.md +1 -1
- package/plugins/loopx/skills/kratos/SKILL.md +1 -1
- package/plugins/loopx/skills/plan/SKILL.md +136 -258
- package/plugins/loopx/skills/refactor-plan/SKILL.md +71 -0
- package/plugins/loopx/skills/review/SKILL.md +72 -105
- package/plugins/loopx/skills/review/code-reviewer.md +168 -0
- package/plugins/loopx/skills/spec/DESIGN_SPEC_TEMPLATE.md +323 -0
- package/plugins/loopx/skills/spec/SKILL.md +76 -0
- package/plugins/loopx/skills/subagent-exec/SKILL.md +282 -0
- package/plugins/loopx/skills/subagent-exec/agents/openai.yaml +3 -0
- package/plugins/loopx/skills/subagent-exec/code-quality-reviewer-prompt.md +25 -0
- package/plugins/loopx/skills/subagent-exec/codex-subagents.md +37 -0
- package/plugins/loopx/skills/subagent-exec/implementer-prompt.md +113 -0
- package/plugins/loopx/skills/subagent-exec/spec-reviewer-prompt.md +61 -0
- package/plugins/loopx/skills/tdd/SKILL.md +1 -1
- package/plugins/loopx/skills/verify/SKILL.md +1 -1
- package/scripts/claude-workflow-hook.mjs +109 -0
- package/scripts/codex-workflow-hook.mjs +2 -5
- package/scripts/install-skills.mjs +3 -3
- package/scripts/verify-skills.mjs +32 -1
- package/skills/RESOLVER.md +26 -17
- package/skills/clarify/SKILL.md +38 -311
- package/skills/debug/SKILL.md +1 -1
- package/skills/exec/SKILL.md +71 -0
- package/skills/finish/SKILL.md +254 -0
- package/skills/fix-review/SKILL.md +216 -0
- package/skills/go-style/SKILL.md +1 -1
- package/skills/kratos/SKILL.md +1 -1
- package/skills/plan/SKILL.md +136 -258
- package/skills/refactor-plan/SKILL.md +71 -0
- package/skills/review/SKILL.md +72 -105
- package/skills/review/code-reviewer.md +168 -0
- package/skills/spec/DESIGN_SPEC_TEMPLATE.md +323 -0
- package/skills/spec/SKILL.md +76 -0
- package/skills/subagent-exec/SKILL.md +282 -0
- package/skills/subagent-exec/agents/openai.yaml +3 -0
- package/skills/subagent-exec/code-quality-reviewer-prompt.md +25 -0
- package/skills/subagent-exec/codex-subagents.md +37 -0
- package/skills/subagent-exec/implementer-prompt.md +113 -0
- package/skills/subagent-exec/spec-reviewer-prompt.md +61 -0
- package/skills/tdd/SKILL.md +1 -1
- package/skills/verify/SKILL.md +1 -1
- package/src/autopilot-runtime.mjs +1 -1
- package/src/cli.mjs +79 -5
- package/src/context-manifest.mjs +2 -2
- package/src/html-views.mjs +457 -95
- package/src/install-discovery.mjs +210 -6
- package/src/next-skill.mjs +2 -4
- package/src/plan-runtime.mjs +572 -93
- package/src/runtime-maintenance.mjs +60 -16
- package/src/workflow.mjs +989 -65
- package/templates/architecture.md +58 -16
- package/templates/development-plan.md +42 -12
- package/plugins/loopx/skills/archive/SKILL.md +0 -55
- package/plugins/loopx/skills/autopilot/SKILL.md +0 -93
- package/plugins/loopx/skills/build/SKILL.md +0 -228
- package/skills/archive/SKILL.md +0 -55
- package/skills/autopilot/SKILL.md +0 -93
- package/skills/build/SKILL.md +0 -228
|
@@ -16,25 +16,48 @@ const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
|
16
16
|
const PROJECT_ROOT = resolve(MODULE_DIR, '..');
|
|
17
17
|
const LOOPX_SKILLS = [
|
|
18
18
|
'clarify',
|
|
19
|
+
'spec',
|
|
19
20
|
'plan',
|
|
20
|
-
'
|
|
21
|
+
'subagent-exec',
|
|
22
|
+
'exec',
|
|
21
23
|
'review',
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
+
'fix-review',
|
|
25
|
+
'finish',
|
|
26
|
+
'refactor-plan',
|
|
24
27
|
'debug',
|
|
25
28
|
'tdd',
|
|
26
29
|
'verify',
|
|
27
30
|
'go-style',
|
|
28
31
|
'kratos',
|
|
29
32
|
];
|
|
33
|
+
const LOOPX_LEGACY_SKILLS = [
|
|
34
|
+
'build',
|
|
35
|
+
'autopilot',
|
|
36
|
+
'archive',
|
|
37
|
+
'writing-plans',
|
|
38
|
+
'executing-plans',
|
|
39
|
+
'subagent-driven-development',
|
|
40
|
+
'requesting-code-review',
|
|
41
|
+
'receiving-code-review',
|
|
42
|
+
'finishing-a-development-branch',
|
|
43
|
+
'request-refactor-plan',
|
|
44
|
+
];
|
|
30
45
|
const LOOPX_INSTALLATION_IDENTITY = 'loopx';
|
|
31
46
|
const LOOPX_MANAGED_SCRIPT_ITEMS = [
|
|
32
47
|
{
|
|
33
48
|
name: 'codex-workflow-hook',
|
|
34
49
|
kind: 'hook',
|
|
50
|
+
targets: ['codex'],
|
|
35
51
|
sourceRelativePath: 'scripts/codex-workflow-hook.mjs',
|
|
36
52
|
targetRelativePath: '.codex/hooks/codex-workflow-hook.mjs',
|
|
37
53
|
},
|
|
54
|
+
{
|
|
55
|
+
name: 'claude-workflow-hook',
|
|
56
|
+
kind: 'hook',
|
|
57
|
+
targets: ['claude'],
|
|
58
|
+
sourceRelativePath: 'scripts/claude-workflow-hook.mjs',
|
|
59
|
+
targetRelativePath: '.claude/hooks/loopx-workflow-hook.mjs',
|
|
60
|
+
},
|
|
38
61
|
];
|
|
39
62
|
const LOOPX_GOVERNED_SOURCE_ITEMS = [
|
|
40
63
|
{
|
|
@@ -105,6 +128,16 @@ export function getInstalledSkillsRoot(env = process.env) {
|
|
|
105
128
|
return resolve(env.LOOPX_SKILLS_ROOT || join(getAgentsRoot(env), 'skills'));
|
|
106
129
|
}
|
|
107
130
|
|
|
131
|
+
export function getClaudeSkillsRoot(env = process.env) {
|
|
132
|
+
const home = resolve(env.LOOPX_HOME || env.HOME || process.cwd());
|
|
133
|
+
return resolve(env.LOOPX_CLAUDE_SKILLS_ROOT || join(home, '.claude', 'skills'));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function getClaudeSettingsPath(env = process.env) {
|
|
137
|
+
const home = resolve(env.LOOPX_HOME || env.HOME || process.cwd());
|
|
138
|
+
return resolve(env.LOOPX_CLAUDE_SETTINGS_PATH || join(home, '.claude', 'settings.json'));
|
|
139
|
+
}
|
|
140
|
+
|
|
108
141
|
export function getSkillLockPath(env = process.env) {
|
|
109
142
|
return resolve(env.LOOPX_SKILL_LOCK_PATH || join(getAgentsRoot(env), '.skill-lock.json'));
|
|
110
143
|
}
|
|
@@ -152,6 +185,10 @@ function installedManagedScriptPath(item, env = process.env) {
|
|
|
152
185
|
return join(installTemplateRoot(env), item.targetRelativePath);
|
|
153
186
|
}
|
|
154
187
|
|
|
188
|
+
function managedScriptItemsForTarget(target) {
|
|
189
|
+
return LOOPX_MANAGED_SCRIPT_ITEMS.filter((item) => !Array.isArray(item.targets) || item.targets.includes(target || 'codex'));
|
|
190
|
+
}
|
|
191
|
+
|
|
155
192
|
async function fileHash(path) {
|
|
156
193
|
const hash = createHash('sha1');
|
|
157
194
|
const stat = await lstat(path);
|
|
@@ -196,6 +233,17 @@ async function writeSkillLock(data, env = process.env) {
|
|
|
196
233
|
await writeFile(path, `${JSON.stringify(data, null, 2)}\n`);
|
|
197
234
|
}
|
|
198
235
|
|
|
236
|
+
async function readJsonFile(path, fallback) {
|
|
237
|
+
if (!existsSync(path)) {
|
|
238
|
+
return jsonClone(fallback);
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
return JSON.parse(await readFile(path, 'utf8'));
|
|
242
|
+
} catch {
|
|
243
|
+
return jsonClone(fallback);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
199
247
|
async function removeInstalledSkill(path) {
|
|
200
248
|
if (!existsSync(path)) {
|
|
201
249
|
return;
|
|
@@ -365,6 +413,7 @@ function buildRegistryRow(record, env = process.env, options = {}) {
|
|
|
365
413
|
installedAt: isoNow(),
|
|
366
414
|
updatedAt: isoNow(),
|
|
367
415
|
skillFolderHash: record.skillFolderHash,
|
|
416
|
+
target: options.target || 'codex',
|
|
368
417
|
provenance: [
|
|
369
418
|
{
|
|
370
419
|
distributionChannel: options.distributionChannel,
|
|
@@ -403,6 +452,20 @@ async function removeStaleOwnedInstall(currentRow) {
|
|
|
403
452
|
await removeInstalledSkill(currentRow.installedPath);
|
|
404
453
|
}
|
|
405
454
|
|
|
455
|
+
async function pruneLegacyLoopxOwnedSkills(nextData, env = process.env) {
|
|
456
|
+
const pruned = [];
|
|
457
|
+
for (const skillName of LOOPX_LEGACY_SKILLS) {
|
|
458
|
+
const row = nextData.skills?.[skillName];
|
|
459
|
+
if (!isLoopxOwnedIdentity(skillName, row, env)) {
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
await removeStaleOwnedInstall(row);
|
|
463
|
+
delete nextData.skills[skillName];
|
|
464
|
+
pruned.push({ skillName, installedPath: row.installedPath });
|
|
465
|
+
}
|
|
466
|
+
return pruned;
|
|
467
|
+
}
|
|
468
|
+
|
|
406
469
|
async function removeInstalledFile(path) {
|
|
407
470
|
if (!existsSync(path)) {
|
|
408
471
|
return;
|
|
@@ -525,7 +588,7 @@ export async function inspectInstallState(env = process.env) {
|
|
|
525
588
|
}
|
|
526
589
|
|
|
527
590
|
const managedArtifacts = {};
|
|
528
|
-
for (const item of
|
|
591
|
+
for (const item of managedScriptItemsForTarget(env.LOOPX_INSTALL_TARGET || 'codex')) {
|
|
529
592
|
const targetPath = installedManagedScriptPath(item, env);
|
|
530
593
|
const sourcePath = projectSourceEntry(item.sourceRelativePath, env);
|
|
531
594
|
if (!existsSync(sourcePath)) {
|
|
@@ -578,7 +641,7 @@ export async function verifyInstallState(env = process.env) {
|
|
|
578
641
|
}
|
|
579
642
|
}
|
|
580
643
|
|
|
581
|
-
for (const item of
|
|
644
|
+
for (const item of managedScriptItemsForTarget(env.LOOPX_INSTALL_TARGET || 'codex')) {
|
|
582
645
|
const info = inspection.managedArtifacts?.[item.name];
|
|
583
646
|
if (!info?.available) {
|
|
584
647
|
continue;
|
|
@@ -604,6 +667,7 @@ export async function installBundledSkills(env = process.env, options = {}) {
|
|
|
604
667
|
const nextData = jsonClone(data);
|
|
605
668
|
nextData.version = nextData.version || 3;
|
|
606
669
|
nextData.skills = nextData.skills || {};
|
|
670
|
+
const pruned = await pruneLegacyLoopxOwnedSkills(nextData, env);
|
|
607
671
|
const baselinePath = getTemplateBaselinePath(env);
|
|
608
672
|
const existingBaseline = await readTemplateBaseline(baselinePath);
|
|
609
673
|
const baselineItemsByPath = new Map((existingBaseline?.items || []).map((item) => [templateItemKey(item), item]));
|
|
@@ -650,7 +714,7 @@ export async function installBundledSkills(env = process.env, options = {}) {
|
|
|
650
714
|
installed.push(row);
|
|
651
715
|
}
|
|
652
716
|
|
|
653
|
-
for (const item of
|
|
717
|
+
for (const item of managedScriptItemsForTarget(options.target || env.LOOPX_INSTALL_TARGET || 'codex')) {
|
|
654
718
|
const { targetPath, sourcePath } = managedScriptTemplatePaths(item, env);
|
|
655
719
|
if (!existsSync(sourcePath)) {
|
|
656
720
|
continue;
|
|
@@ -702,6 +766,7 @@ export async function installBundledSkills(env = process.env, options = {}) {
|
|
|
702
766
|
installed,
|
|
703
767
|
conflicts,
|
|
704
768
|
skipped,
|
|
769
|
+
pruned,
|
|
705
770
|
templateGovernance,
|
|
706
771
|
inspection: await inspectInstallState(env),
|
|
707
772
|
};
|
|
@@ -711,4 +776,143 @@ export async function repairBundledSkills(env = process.env) {
|
|
|
711
776
|
return installBundledSkills(env);
|
|
712
777
|
}
|
|
713
778
|
|
|
779
|
+
function codexInstallEnv(env = process.env) {
|
|
780
|
+
if (!env.LOOPX_INSTALL_CUSTOM_DIR) {
|
|
781
|
+
return {
|
|
782
|
+
...env,
|
|
783
|
+
LOOPX_INSTALL_TARGET: 'codex',
|
|
784
|
+
};
|
|
785
|
+
}
|
|
786
|
+
const root = resolve(env.LOOPX_INSTALL_CUSTOM_DIR);
|
|
787
|
+
return {
|
|
788
|
+
...env,
|
|
789
|
+
LOOPX_INSTALL_TARGET: 'codex',
|
|
790
|
+
LOOPX_SKILLS_ROOT: root,
|
|
791
|
+
LOOPX_SKILL_LOCK_PATH: join(dirname(root), '.loopx-skill-lock.json'),
|
|
792
|
+
LOOPX_TEMPLATE_BASELINE_PATH: join(dirname(root), '.loopx-template-hashes.json'),
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
function claudeInstallEnv(env = process.env, options = {}) {
|
|
797
|
+
const root = options.project === true
|
|
798
|
+
? join(resolve(env.LOOPX_INSTALL_CWD || process.cwd()), '.claude', 'skills')
|
|
799
|
+
: getClaudeSkillsRoot(env);
|
|
800
|
+
return {
|
|
801
|
+
...env,
|
|
802
|
+
LOOPX_INSTALL_TARGET: 'claude',
|
|
803
|
+
LOOPX_SKILLS_ROOT: options.dir || root,
|
|
804
|
+
LOOPX_SKILL_LOCK_PATH: options.lockPath || join(dirname(root), '.loopx-skill-lock.json'),
|
|
805
|
+
LOOPX_TEMPLATE_BASELINE_PATH: options.templateBaselinePath || join(dirname(root), '.loopx-template-hashes.json'),
|
|
806
|
+
LOOPX_DISTRIBUTION_CHANNEL: options.distributionChannel || 'claude',
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async function mergeClaudeHookSettings(env = process.env, options = {}) {
|
|
811
|
+
const settingsPath = options.project === true
|
|
812
|
+
? join(resolve(env.LOOPX_INSTALL_CWD || process.cwd()), '.claude', 'settings.json')
|
|
813
|
+
: getClaudeSettingsPath(env);
|
|
814
|
+
const hookPath = join(dirname(settingsPath), 'hooks', 'loopx-workflow-hook.mjs');
|
|
815
|
+
const settings = await readJsonFile(settingsPath, {});
|
|
816
|
+
const hooks = settings.hooks && typeof settings.hooks === 'object' && !Array.isArray(settings.hooks)
|
|
817
|
+
? settings.hooks
|
|
818
|
+
: {};
|
|
819
|
+
const promptHooks = Array.isArray(hooks.UserPromptSubmit) ? hooks.UserPromptSubmit : [];
|
|
820
|
+
const command = `node ${JSON.stringify(hookPath)}`;
|
|
821
|
+
const hasCommand = promptHooks.some((entry) => {
|
|
822
|
+
if (typeof entry === 'string') {
|
|
823
|
+
return entry === command;
|
|
824
|
+
}
|
|
825
|
+
return Array.isArray(entry?.hooks)
|
|
826
|
+
&& entry.hooks.some((hook) => hook?.command === command);
|
|
827
|
+
});
|
|
828
|
+
const nextPromptHooks = hasCommand
|
|
829
|
+
? promptHooks
|
|
830
|
+
: [
|
|
831
|
+
...promptHooks,
|
|
832
|
+
{
|
|
833
|
+
matcher: '',
|
|
834
|
+
hooks: [
|
|
835
|
+
{
|
|
836
|
+
type: 'command',
|
|
837
|
+
command,
|
|
838
|
+
},
|
|
839
|
+
],
|
|
840
|
+
},
|
|
841
|
+
];
|
|
842
|
+
const nextSettings = {
|
|
843
|
+
...settings,
|
|
844
|
+
hooks: {
|
|
845
|
+
...hooks,
|
|
846
|
+
UserPromptSubmit: nextPromptHooks,
|
|
847
|
+
},
|
|
848
|
+
};
|
|
849
|
+
await ensureDir(dirname(settingsPath));
|
|
850
|
+
await writeFile(settingsPath, `${JSON.stringify(nextSettings, null, 2)}\n`);
|
|
851
|
+
return { settingsPath, hookPath, command };
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
export async function installSkillsForTargets(env = process.env, options = {}) {
|
|
855
|
+
const requestedTargets = Array.isArray(options.targets) && options.targets.length > 0
|
|
856
|
+
? options.targets
|
|
857
|
+
: ['codex', 'claude'];
|
|
858
|
+
const results = {};
|
|
859
|
+
for (const target of requestedTargets) {
|
|
860
|
+
if (target === 'codex') {
|
|
861
|
+
const codexEnv = codexInstallEnv({
|
|
862
|
+
...env,
|
|
863
|
+
LOOPX_INSTALL_CUSTOM_DIR: options.dir,
|
|
864
|
+
});
|
|
865
|
+
results.codex = await installBundledSkills(codexEnv, {
|
|
866
|
+
...options,
|
|
867
|
+
dir: undefined,
|
|
868
|
+
target: 'codex',
|
|
869
|
+
distributionChannel: options.distributionChannel || env.LOOPX_DISTRIBUTION_CHANNEL || 'npm',
|
|
870
|
+
});
|
|
871
|
+
continue;
|
|
872
|
+
}
|
|
873
|
+
if (target === 'claude') {
|
|
874
|
+
const claudeEnv = claudeInstallEnv(env, options);
|
|
875
|
+
results.claude = await installBundledSkills(claudeEnv, {
|
|
876
|
+
...options,
|
|
877
|
+
target: 'claude',
|
|
878
|
+
distributionChannel: options.distributionChannel || 'claude',
|
|
879
|
+
});
|
|
880
|
+
results.claudeHook = await mergeClaudeHookSettings(env, options);
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
throw new Error(`unknown_install_target:${target}`);
|
|
884
|
+
}
|
|
885
|
+
return {
|
|
886
|
+
ok: Object.values(results).every((result) => result?.ok !== false),
|
|
887
|
+
targets: requestedTargets,
|
|
888
|
+
results,
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
export async function verifyInstallTargets(env = process.env, options = {}) {
|
|
893
|
+
const requestedTargets = Array.isArray(options.targets) && options.targets.length > 0
|
|
894
|
+
? options.targets
|
|
895
|
+
: ['codex', 'claude'];
|
|
896
|
+
const results = {};
|
|
897
|
+
for (const target of requestedTargets) {
|
|
898
|
+
if (target === 'codex') {
|
|
899
|
+
results.codex = await verifyInstallState(codexInstallEnv({
|
|
900
|
+
...env,
|
|
901
|
+
LOOPX_INSTALL_CUSTOM_DIR: options.dir,
|
|
902
|
+
}));
|
|
903
|
+
continue;
|
|
904
|
+
}
|
|
905
|
+
if (target === 'claude') {
|
|
906
|
+
results.claude = await verifyInstallState(claudeInstallEnv(env, options));
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
throw new Error(`unknown_install_target:${target}`);
|
|
910
|
+
}
|
|
911
|
+
return {
|
|
912
|
+
ok: Object.values(results).every((result) => result?.ok !== false),
|
|
913
|
+
targets: requestedTargets,
|
|
914
|
+
results,
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
|
|
714
918
|
export const LOOPX_BUNDLED_SKILLS = LOOPX_SKILLS;
|
package/src/next-skill.mjs
CHANGED
|
@@ -9,9 +9,7 @@ export function nextSkillCommand(state) {
|
|
|
9
9
|
&& state.clarify_non_goals_resolved === true
|
|
10
10
|
&& state.clarify_decision_boundaries_resolved === true
|
|
11
11
|
&& state.clarify_pressure_pass_complete === true
|
|
12
|
-
|
|
13
|
-
&& typeof state.clarify_target_ambiguity_threshold === 'number'
|
|
14
|
-
&& state.clarify_ambiguity_score <= state.clarify_target_ambiguity_threshold) {
|
|
12
|
+
) {
|
|
15
13
|
return `$plan ${state.slug}`;
|
|
16
14
|
}
|
|
17
15
|
if (state.current_stage === 'done'
|
|
@@ -30,7 +28,7 @@ export function nextSkillCommand(state) {
|
|
|
30
28
|
return null;
|
|
31
29
|
}
|
|
32
30
|
if (state.current_stage === 'plan' && Array.isArray(state.plan_blockers) && state.plan_blockers.length === 0) {
|
|
33
|
-
return `$build .loopx/plans/
|
|
31
|
+
return `$build .loopx/plans/requirements-snapshot-${state.slug}.md`;
|
|
34
32
|
}
|
|
35
33
|
if (state.current_stage === 'build'
|
|
36
34
|
&& state.stage_status === 'awaiting-approval'
|