@ai-content-space/loopx 0.2.8 → 0.2.10

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 (105) hide show
  1. package/README.md +26 -9
  2. package/README.zh-CN.md +26 -9
  3. package/docs/loopx/design/loopx-skill-suite-v1-design.md +12 -0
  4. package/docs/loopx/plans/2026-06-14-loopx-spec-memory-context-loading.md +948 -0
  5. package/docs/loopx/plans/2026-06-15-support-lens-skills-migration.md +1153 -0
  6. package/package.json +6 -1
  7. package/plugins/loopx/.codex-plugin/plugin.json +1 -1
  8. package/plugins/loopx/skills/api-designer/SKILL.md +232 -0
  9. package/plugins/loopx/skills/api-designer/references/error-handling.md +541 -0
  10. package/plugins/loopx/skills/api-designer/references/openapi.md +824 -0
  11. package/plugins/loopx/skills/api-designer/references/pagination.md +494 -0
  12. package/plugins/loopx/skills/api-designer/references/rest-patterns.md +335 -0
  13. package/plugins/loopx/skills/api-designer/references/versioning.md +391 -0
  14. package/plugins/loopx/skills/architecture-designer/SKILL.md +117 -0
  15. package/plugins/loopx/skills/architecture-designer/references/adr-template.md +116 -0
  16. package/plugins/loopx/skills/architecture-designer/references/architecture-patterns.md +346 -0
  17. package/plugins/loopx/skills/architecture-designer/references/database-selection.md +102 -0
  18. package/plugins/loopx/skills/architecture-designer/references/nfr-checklist.md +212 -0
  19. package/plugins/loopx/skills/architecture-designer/references/system-design.md +313 -0
  20. package/plugins/loopx/skills/clarify/SKILL.md +12 -1
  21. package/plugins/loopx/skills/cli-developer/SKILL.md +124 -0
  22. package/plugins/loopx/skills/cli-developer/references/design-patterns.md +221 -0
  23. package/plugins/loopx/skills/cli-developer/references/go-cli.md +540 -0
  24. package/plugins/loopx/skills/cli-developer/references/node-cli.md +383 -0
  25. package/plugins/loopx/skills/cli-developer/references/python-cli.md +422 -0
  26. package/plugins/loopx/skills/cli-developer/references/ux-patterns.md +448 -0
  27. package/plugins/loopx/skills/debug/SKILL.md +1 -1
  28. package/plugins/loopx/skills/doc-readability/SKILL.md +1 -1
  29. package/plugins/loopx/skills/exec/SKILL.md +1 -1
  30. package/plugins/loopx/skills/final-review/SKILL.md +1 -1
  31. package/plugins/loopx/skills/finish/SKILL.md +1 -1
  32. package/plugins/loopx/skills/fix-review/SKILL.md +1 -1
  33. package/plugins/loopx/skills/go-style/SKILL.md +1 -1
  34. package/plugins/loopx/skills/kratos/SKILL.md +2 -1
  35. package/plugins/loopx/skills/plan-to-exec/SKILL.md +12 -1
  36. package/plugins/loopx/skills/refactor-plan/SKILL.md +1 -1
  37. package/plugins/loopx/skills/requirement-analyzer/SKILL.md +161 -0
  38. package/plugins/loopx/skills/requirement-analyzer/references/example-reports.md +170 -0
  39. package/plugins/loopx/skills/requirement-analyzer/references/prd-gap-checklist.md +167 -0
  40. package/plugins/loopx/skills/requirement-analyzer/references/readiness-rubric.md +70 -0
  41. package/plugins/loopx/skills/requirement-analyzer/references/report-template.md +83 -0
  42. package/plugins/loopx/skills/review/SKILL.md +1 -1
  43. package/plugins/loopx/skills/spec/SKILL.md +12 -1
  44. package/plugins/loopx/skills/sql-style/SKILL.md +108 -0
  45. package/plugins/loopx/skills/sql-style/references/database-design.md +402 -0
  46. package/plugins/loopx/skills/sql-style/references/dialect-differences.md +419 -0
  47. package/plugins/loopx/skills/sql-style/references/optimization.md +384 -0
  48. package/plugins/loopx/skills/sql-style/references/query-patterns.md +285 -0
  49. package/plugins/loopx/skills/sql-style/references/window-functions.md +328 -0
  50. package/plugins/loopx/skills/subagent-exec/SKILL.md +1 -1
  51. package/plugins/loopx/skills/tdd/SKILL.md +1 -1
  52. package/plugins/loopx/skills/verify/SKILL.md +1 -1
  53. package/scripts/verify-skills.mjs +0 -2
  54. package/skills/RESOLVER.md +8 -1
  55. package/skills/api-designer/SKILL.md +232 -0
  56. package/skills/api-designer/references/error-handling.md +541 -0
  57. package/skills/api-designer/references/openapi.md +824 -0
  58. package/skills/api-designer/references/pagination.md +494 -0
  59. package/skills/api-designer/references/rest-patterns.md +335 -0
  60. package/skills/api-designer/references/versioning.md +391 -0
  61. package/skills/architecture-designer/SKILL.md +117 -0
  62. package/skills/architecture-designer/references/adr-template.md +116 -0
  63. package/skills/architecture-designer/references/architecture-patterns.md +346 -0
  64. package/skills/architecture-designer/references/database-selection.md +102 -0
  65. package/skills/architecture-designer/references/nfr-checklist.md +212 -0
  66. package/skills/architecture-designer/references/system-design.md +313 -0
  67. package/skills/clarify/SKILL.md +12 -1
  68. package/skills/cli-developer/SKILL.md +124 -0
  69. package/skills/cli-developer/references/design-patterns.md +221 -0
  70. package/skills/cli-developer/references/go-cli.md +540 -0
  71. package/skills/cli-developer/references/node-cli.md +383 -0
  72. package/skills/cli-developer/references/python-cli.md +422 -0
  73. package/skills/cli-developer/references/ux-patterns.md +448 -0
  74. package/skills/debug/SKILL.md +1 -1
  75. package/skills/doc-readability/SKILL.md +1 -1
  76. package/skills/exec/SKILL.md +1 -1
  77. package/skills/final-review/SKILL.md +1 -1
  78. package/skills/finish/SKILL.md +1 -1
  79. package/skills/fix-review/SKILL.md +1 -1
  80. package/skills/go-style/SKILL.md +1 -1
  81. package/skills/kratos/SKILL.md +2 -1
  82. package/skills/plan-to-exec/SKILL.md +12 -1
  83. package/skills/refactor-plan/SKILL.md +1 -1
  84. package/skills/requirement-analyzer/SKILL.md +161 -0
  85. package/skills/requirement-analyzer/references/example-reports.md +170 -0
  86. package/skills/requirement-analyzer/references/prd-gap-checklist.md +167 -0
  87. package/skills/requirement-analyzer/references/readiness-rubric.md +70 -0
  88. package/skills/requirement-analyzer/references/report-template.md +83 -0
  89. package/skills/review/SKILL.md +1 -1
  90. package/skills/spec/SKILL.md +12 -1
  91. package/skills/sql-style/SKILL.md +108 -0
  92. package/skills/sql-style/references/database-design.md +402 -0
  93. package/skills/sql-style/references/dialect-differences.md +419 -0
  94. package/skills/sql-style/references/optimization.md +384 -0
  95. package/skills/sql-style/references/query-patterns.md +285 -0
  96. package/skills/sql-style/references/window-functions.md +328 -0
  97. package/skills/subagent-exec/SKILL.md +1 -1
  98. package/skills/tdd/SKILL.md +1 -1
  99. package/skills/verify/SKILL.md +1 -1
  100. package/src/cli.mjs +4 -1
  101. package/src/context-manifest.mjs +51 -1
  102. package/src/install-discovery.mjs +114 -0
  103. package/src/loopx-context-artifacts.mjs +114 -0
  104. package/src/project-discovery.mjs +1 -0
  105. package/src/workflow.mjs +47 -3
@@ -0,0 +1,114 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile, readdir } from 'node:fs/promises';
3
+ import { basename, join, relative, resolve } from 'node:path';
4
+
5
+ const MAX_SPEC_CONTEXT_FILES = 12;
6
+
7
+ function displayPath(cwd, path) {
8
+ const rel = relative(cwd, path);
9
+ return rel && !rel.startsWith('..') ? rel : path;
10
+ }
11
+
12
+ function normalizeChangedFiles(files = []) {
13
+ return Array.isArray(files)
14
+ ? files.map((file) => String(file || '').trim()).filter(Boolean)
15
+ : [];
16
+ }
17
+
18
+ async function listMarkdownFiles(root) {
19
+ if (!existsSync(root)) {
20
+ return [];
21
+ }
22
+ const found = [];
23
+ async function walk(dir) {
24
+ const entries = await readdir(dir, { withFileTypes: true });
25
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
26
+ const path = join(dir, entry.name);
27
+ if (entry.isDirectory()) {
28
+ await walk(path);
29
+ continue;
30
+ }
31
+ if (entry.isFile() && /\.md$/i.test(entry.name)) {
32
+ found.push(path);
33
+ }
34
+ }
35
+ }
36
+ await walk(root);
37
+ return found;
38
+ }
39
+
40
+ function pathParts(value) {
41
+ return String(value || '')
42
+ .toLowerCase()
43
+ .split(/[^a-z0-9]+/)
44
+ .filter((part) => part.length >= 3);
45
+ }
46
+
47
+ function frontmatterAppliesTo(text) {
48
+ if (!String(text || '').startsWith('---\n')) {
49
+ return [];
50
+ }
51
+ const end = text.indexOf('\n---\n', 4);
52
+ if (end === -1) {
53
+ return [];
54
+ }
55
+ const lines = text.slice(4, end).split('\n');
56
+ const values = [];
57
+ let inAppliesTo = false;
58
+ for (const line of lines) {
59
+ if (/^applies_to:\s*$/.test(line)) {
60
+ inAppliesTo = true;
61
+ continue;
62
+ }
63
+ if (inAppliesTo && /^\s+-\s+/.test(line)) {
64
+ values.push(line.replace(/^\s+-\s+/, '').trim().replace(/^['"]|['"]$/g, ''));
65
+ continue;
66
+ }
67
+ if (inAppliesTo && /^\S/.test(line)) {
68
+ inAppliesTo = false;
69
+ }
70
+ }
71
+ return values.filter(Boolean);
72
+ }
73
+
74
+ function appliesToChangedFile(pattern, changedFile) {
75
+ const normalizedPattern = String(pattern || '').replace(/\*\*?\/?/g, '').replace(/\/+$/, '');
76
+ const normalizedFile = String(changedFile || '');
77
+ return normalizedPattern && normalizedFile.includes(normalizedPattern);
78
+ }
79
+
80
+ async function specRecord(cwd, path, changedFiles) {
81
+ const text = await readFile(path, 'utf8');
82
+ const appliesTo = frontmatterAppliesTo(text);
83
+ const stemParts = pathParts(basename(path, '.md'));
84
+ const changedParts = new Set(changedFiles.flatMap(pathParts));
85
+ const filenameMatch = stemParts.some((part) => changedParts.has(part));
86
+ const appliesToMatch = appliesTo.some((pattern) => changedFiles.some((file) => appliesToChangedFile(pattern, file)));
87
+ const isIndex = /(^|\/)index\.md$/i.test(path);
88
+ const isInbox = /(^|\/)inbox\.md$/i.test(path);
89
+ return {
90
+ path: displayPath(cwd, path),
91
+ appliesTo,
92
+ relevant: isIndex || isInbox || filenameMatch || appliesToMatch || changedFiles.length === 0,
93
+ };
94
+ }
95
+
96
+ export async function discoverLoopxContextArtifacts(cwd, options = {}) {
97
+ const root = resolve(cwd);
98
+ const changedFiles = normalizeChangedFiles(options.changedFiles);
99
+ const specsRootPath = join(root, 'docs', 'loopx', 'specs');
100
+ const specPaths = await listMarkdownFiles(specsRootPath);
101
+ const records = await Promise.all(specPaths.map((path) => specRecord(root, path, changedFiles)));
102
+ const relevantSpecs = records
103
+ .filter((record) => record.relevant)
104
+ .sort((left, right) => left.path.localeCompare(right.path))
105
+ .slice(0, MAX_SPEC_CONTEXT_FILES);
106
+ const memorySummaryPath = join(root, '.loopx', 'memory', 'MEMORY.md');
107
+ const memoryIndexPath = join(root, '.loopx', 'memory', 'index.jsonl');
108
+ return {
109
+ specsRoot: existsSync(specsRootPath) ? displayPath(root, specsRootPath) : null,
110
+ specFiles: relevantSpecs,
111
+ memorySummary: existsSync(memorySummaryPath) ? { path: displayPath(root, memorySummaryPath) } : null,
112
+ memoryIndex: existsSync(memoryIndexPath) ? { path: displayPath(root, memoryIndexPath) } : null,
113
+ };
114
+ }
@@ -67,6 +67,7 @@ async function discoverSpecSources(cwd) {
67
67
  candidate(join(cwd, 'specs'), 'specs'),
68
68
  candidate(join(cwd, 'docs', 'changes'), 'docs/changes'),
69
69
  candidate(join(cwd, 'docs', 'specs'), 'docs/specs'),
70
+ candidate(join(cwd, 'docs', 'loopx', 'specs'), 'docs/loopx/specs'),
70
71
  candidate(join(cwd, 'docs', 'adr'), 'docs/adr'),
71
72
  candidate(join(cwd, 'docs', 'rfcs'), 'docs/rfcs'),
72
73
  ]);
package/src/workflow.mjs CHANGED
@@ -20,6 +20,7 @@ import { inspectProjectConventions } from './project-discovery.mjs';
20
20
  import { createDefaultReviewAdapter } from './review-runtime.mjs';
21
21
  import { appendWorkspaceJournal } from './workspace-memory.mjs';
22
22
  import { inspectWorkspaceContext, setupWorkspaceContext } from './workspace-context.mjs';
23
+ import { discoverLoopxContextArtifacts } from './loopx-context-artifacts.mjs';
23
24
 
24
25
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
25
26
  const WORKSPACE_SCHEMA_VERSION = 1;
@@ -325,9 +326,6 @@ function compactPlanningText(text, { html = false } = {}) {
325
326
  async function readPlanSourceText(cwd, state, sourceSpecPath) {
326
327
  const sourceText = await readFile(sourceSpecPath, 'utf8');
327
328
  const sourceDocumentPaths = sourceDocumentPathsFromSpecAndState(sourceSpecPath, sourceText, state);
328
- if (sourceDocumentPaths.length === 0) {
329
- return { sourceText, sourceDocumentPaths: [] };
330
- }
331
329
 
332
330
  const parts = [sourceText.trimEnd()];
333
331
  const loaded = [];
@@ -350,6 +348,11 @@ async function readPlanSourceText(cwd, state, sourceSpecPath) {
350
348
  break;
351
349
  }
352
350
  }
351
+ const repoContext = await readLoopxRepoContextText(cwd, sourceSpecPath);
352
+ if (repoContext.text) {
353
+ parts.push(repoContext.text);
354
+ loaded.push(...repoContext.paths);
355
+ }
353
356
 
354
357
  return {
355
358
  sourceText: parts.join('\n\n').slice(0, MAX_PLAN_SOURCE_BUNDLE_CHARS),
@@ -357,6 +360,47 @@ async function readPlanSourceText(cwd, state, sourceSpecPath) {
357
360
  };
358
361
  }
359
362
 
363
+ async function readLoopxRepoContextText(cwd, sourceSpecPath) {
364
+ const artifacts = await discoverLoopxContextArtifacts(cwd, {
365
+ changedFiles: [relative(cwd, sourceSpecPath)],
366
+ });
367
+ const paths = [
368
+ ...artifacts.specFiles.map((item) => item.path),
369
+ artifacts.memorySummary?.path,
370
+ ].filter(Boolean);
371
+ if (paths.length === 0) {
372
+ return { text: '', paths: [] };
373
+ }
374
+ const sections = [];
375
+ const loaded = [];
376
+ for (const display of paths) {
377
+ const absolute = resolve(cwd, display);
378
+ if (!existsSync(absolute)) {
379
+ continue;
380
+ }
381
+ const raw = await readFile(absolute, 'utf8');
382
+ loaded.push(absolute);
383
+ sections.push([
384
+ `# loopx context: ${display}`,
385
+ '',
386
+ compactPlanningText(raw),
387
+ ].join('\n'));
388
+ }
389
+ if (sections.length === 0) {
390
+ return { text: '', paths: [] };
391
+ }
392
+ return {
393
+ text: [
394
+ '# loopx Repo Specs And Memory Context',
395
+ '',
396
+ 'Current task instructions and named source documents have priority. Repo specs are binding long-lived rules. Memory is advisory.',
397
+ '',
398
+ ...sections,
399
+ ].join('\n\n'),
400
+ paths: loaded,
401
+ };
402
+ }
403
+
360
404
  function frontmatterBlock(values) {
361
405
  const lines = ['---'];
362
406
  for (const [key, value] of Object.entries(values)) {