@ai-content-space/loopx 0.1.10 → 0.2.1

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.
Files changed (77) hide show
  1. package/AGENTS.md +49 -0
  2. package/README.md +69 -448
  3. package/README.zh-CN.md +69 -459
  4. package/docs/loopx/design/loopx-skill-suite-v1-design.md +80 -0
  5. package/docs/loopx/plans/loopx-skill-suite-v1-implementation.md +81 -0
  6. package/package.json +7 -3
  7. package/plugins/loopx/.codex-plugin/plugin.json +4 -4
  8. package/plugins/loopx/skills/clarify/SKILL.md +38 -311
  9. package/plugins/loopx/skills/debug/SKILL.md +1 -1
  10. package/plugins/loopx/skills/exec/SKILL.md +71 -0
  11. package/plugins/loopx/skills/finish/SKILL.md +349 -0
  12. package/plugins/loopx/skills/fix-review/SKILL.md +216 -0
  13. package/plugins/loopx/skills/go-style/SKILL.md +2 -2
  14. package/plugins/loopx/skills/kratos/SKILL.md +1 -1
  15. package/plugins/loopx/skills/plan/SKILL.md +138 -271
  16. package/plugins/loopx/skills/refactor-plan/SKILL.md +71 -0
  17. package/plugins/loopx/skills/review/SKILL.md +72 -105
  18. package/plugins/loopx/skills/review/code-reviewer.md +168 -0
  19. package/plugins/loopx/skills/spec/DESIGN_SPEC_TEMPLATE.md +323 -0
  20. package/plugins/loopx/skills/spec/SKILL.md +76 -0
  21. package/plugins/loopx/skills/subagent-exec/SKILL.md +282 -0
  22. package/plugins/loopx/skills/subagent-exec/agents/openai.yaml +3 -0
  23. package/plugins/loopx/skills/subagent-exec/code-quality-reviewer-prompt.md +25 -0
  24. package/plugins/loopx/skills/subagent-exec/codex-subagents.md +37 -0
  25. package/plugins/loopx/skills/subagent-exec/implementer-prompt.md +113 -0
  26. package/plugins/loopx/skills/subagent-exec/spec-reviewer-prompt.md +61 -0
  27. package/plugins/loopx/skills/tdd/SKILL.md +1 -1
  28. package/plugins/loopx/skills/verify/SKILL.md +1 -1
  29. package/scripts/claude-workflow-hook.mjs +109 -0
  30. package/scripts/codex-workflow-hook.mjs +2 -5
  31. package/scripts/install-skills.mjs +3 -3
  32. package/scripts/verify-skills.mjs +34 -2
  33. package/skills/RESOLVER.md +22 -17
  34. package/skills/clarify/SKILL.md +38 -311
  35. package/skills/debug/SKILL.md +1 -1
  36. package/skills/exec/SKILL.md +71 -0
  37. package/skills/finish/SKILL.md +349 -0
  38. package/skills/fix-review/SKILL.md +216 -0
  39. package/skills/go-style/SKILL.md +2 -2
  40. package/skills/kratos/SKILL.md +1 -1
  41. package/skills/plan/SKILL.md +138 -271
  42. package/skills/refactor-plan/SKILL.md +71 -0
  43. package/skills/review/SKILL.md +72 -105
  44. package/skills/review/code-reviewer.md +168 -0
  45. package/skills/spec/DESIGN_SPEC_TEMPLATE.md +323 -0
  46. package/skills/spec/SKILL.md +76 -0
  47. package/skills/subagent-exec/SKILL.md +282 -0
  48. package/skills/subagent-exec/agents/openai.yaml +3 -0
  49. package/skills/subagent-exec/code-quality-reviewer-prompt.md +25 -0
  50. package/skills/subagent-exec/codex-subagents.md +37 -0
  51. package/skills/subagent-exec/implementer-prompt.md +113 -0
  52. package/skills/subagent-exec/spec-reviewer-prompt.md +61 -0
  53. package/skills/tdd/SKILL.md +1 -1
  54. package/skills/verify/SKILL.md +1 -1
  55. package/src/autopilot-runtime.mjs +1 -1
  56. package/src/cli.mjs +78 -7
  57. package/src/context-manifest.mjs +2 -2
  58. package/src/html-views.mjs +129 -195
  59. package/src/install-discovery.mjs +210 -6
  60. package/src/next-skill.mjs +2 -4
  61. package/src/plan-runtime.mjs +219 -93
  62. package/src/runtime-maintenance.mjs +5 -2
  63. package/src/workflow.mjs +749 -71
  64. package/templates/architecture.md +58 -16
  65. package/templates/development-plan.md +42 -12
  66. package/plugins/loopx/scripts/plugin-install.test.mjs +0 -125
  67. package/plugins/loopx/skills/archive/SKILL.md +0 -55
  68. package/plugins/loopx/skills/autopilot/SKILL.md +0 -93
  69. package/plugins/loopx/skills/build/SKILL.md +0 -228
  70. package/skills/ai-slop-cleaner/SKILL.md +0 -114
  71. package/skills/archive/SKILL.md +0 -55
  72. package/skills/autopilot/SKILL.md +0 -93
  73. package/skills/autoresearch/SKILL.md +0 -68
  74. package/skills/build/SKILL.md +0 -228
  75. package/skills/deep-interview/SKILL.md +0 -461
  76. package/skills/ralph/SKILL.md +0 -271
  77. package/skills/ralplan/SKILL.md +0 -49
@@ -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
- 'build',
21
+ 'subagent-exec',
22
+ 'exec',
21
23
  'review',
22
- 'autopilot',
23
- 'archive',
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 LOOPX_MANAGED_SCRIPT_ITEMS) {
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 LOOPX_MANAGED_SCRIPT_ITEMS) {
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 LOOPX_MANAGED_SCRIPT_ITEMS) {
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;
@@ -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
- && typeof state.clarify_ambiguity_score === 'number'
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/prd-${state.slug}.md`;
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'