@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.
- package/README.md +26 -9
- package/README.zh-CN.md +26 -9
- package/docs/loopx/design/loopx-skill-suite-v1-design.md +12 -0
- package/docs/loopx/plans/2026-06-14-loopx-spec-memory-context-loading.md +948 -0
- package/docs/loopx/plans/2026-06-15-support-lens-skills-migration.md +1153 -0
- package/package.json +6 -1
- package/plugins/loopx/.codex-plugin/plugin.json +1 -1
- package/plugins/loopx/skills/api-designer/SKILL.md +232 -0
- package/plugins/loopx/skills/api-designer/references/error-handling.md +541 -0
- package/plugins/loopx/skills/api-designer/references/openapi.md +824 -0
- package/plugins/loopx/skills/api-designer/references/pagination.md +494 -0
- package/plugins/loopx/skills/api-designer/references/rest-patterns.md +335 -0
- package/plugins/loopx/skills/api-designer/references/versioning.md +391 -0
- package/plugins/loopx/skills/architecture-designer/SKILL.md +117 -0
- package/plugins/loopx/skills/architecture-designer/references/adr-template.md +116 -0
- package/plugins/loopx/skills/architecture-designer/references/architecture-patterns.md +346 -0
- package/plugins/loopx/skills/architecture-designer/references/database-selection.md +102 -0
- package/plugins/loopx/skills/architecture-designer/references/nfr-checklist.md +212 -0
- package/plugins/loopx/skills/architecture-designer/references/system-design.md +313 -0
- package/plugins/loopx/skills/clarify/SKILL.md +12 -1
- package/plugins/loopx/skills/cli-developer/SKILL.md +124 -0
- package/plugins/loopx/skills/cli-developer/references/design-patterns.md +221 -0
- package/plugins/loopx/skills/cli-developer/references/go-cli.md +540 -0
- package/plugins/loopx/skills/cli-developer/references/node-cli.md +383 -0
- package/plugins/loopx/skills/cli-developer/references/python-cli.md +422 -0
- package/plugins/loopx/skills/cli-developer/references/ux-patterns.md +448 -0
- package/plugins/loopx/skills/debug/SKILL.md +1 -1
- package/plugins/loopx/skills/doc-readability/SKILL.md +1 -1
- package/plugins/loopx/skills/exec/SKILL.md +1 -1
- package/plugins/loopx/skills/final-review/SKILL.md +1 -1
- package/plugins/loopx/skills/finish/SKILL.md +1 -1
- package/plugins/loopx/skills/fix-review/SKILL.md +1 -1
- package/plugins/loopx/skills/go-style/SKILL.md +1 -1
- package/plugins/loopx/skills/kratos/SKILL.md +2 -1
- package/plugins/loopx/skills/plan-to-exec/SKILL.md +12 -1
- package/plugins/loopx/skills/refactor-plan/SKILL.md +1 -1
- package/plugins/loopx/skills/requirement-analyzer/SKILL.md +161 -0
- package/plugins/loopx/skills/requirement-analyzer/references/example-reports.md +170 -0
- package/plugins/loopx/skills/requirement-analyzer/references/prd-gap-checklist.md +167 -0
- package/plugins/loopx/skills/requirement-analyzer/references/readiness-rubric.md +70 -0
- package/plugins/loopx/skills/requirement-analyzer/references/report-template.md +83 -0
- package/plugins/loopx/skills/review/SKILL.md +1 -1
- package/plugins/loopx/skills/spec/SKILL.md +12 -1
- package/plugins/loopx/skills/sql-style/SKILL.md +108 -0
- package/plugins/loopx/skills/sql-style/references/database-design.md +402 -0
- package/plugins/loopx/skills/sql-style/references/dialect-differences.md +419 -0
- package/plugins/loopx/skills/sql-style/references/optimization.md +384 -0
- package/plugins/loopx/skills/sql-style/references/query-patterns.md +285 -0
- package/plugins/loopx/skills/sql-style/references/window-functions.md +328 -0
- package/plugins/loopx/skills/subagent-exec/SKILL.md +1 -1
- package/plugins/loopx/skills/tdd/SKILL.md +1 -1
- package/plugins/loopx/skills/verify/SKILL.md +1 -1
- package/scripts/verify-skills.mjs +0 -2
- package/skills/RESOLVER.md +8 -1
- package/skills/api-designer/SKILL.md +232 -0
- package/skills/api-designer/references/error-handling.md +541 -0
- package/skills/api-designer/references/openapi.md +824 -0
- package/skills/api-designer/references/pagination.md +494 -0
- package/skills/api-designer/references/rest-patterns.md +335 -0
- package/skills/api-designer/references/versioning.md +391 -0
- package/skills/architecture-designer/SKILL.md +117 -0
- package/skills/architecture-designer/references/adr-template.md +116 -0
- package/skills/architecture-designer/references/architecture-patterns.md +346 -0
- package/skills/architecture-designer/references/database-selection.md +102 -0
- package/skills/architecture-designer/references/nfr-checklist.md +212 -0
- package/skills/architecture-designer/references/system-design.md +313 -0
- package/skills/clarify/SKILL.md +12 -1
- package/skills/cli-developer/SKILL.md +124 -0
- package/skills/cli-developer/references/design-patterns.md +221 -0
- package/skills/cli-developer/references/go-cli.md +540 -0
- package/skills/cli-developer/references/node-cli.md +383 -0
- package/skills/cli-developer/references/python-cli.md +422 -0
- package/skills/cli-developer/references/ux-patterns.md +448 -0
- package/skills/debug/SKILL.md +1 -1
- package/skills/doc-readability/SKILL.md +1 -1
- package/skills/exec/SKILL.md +1 -1
- package/skills/final-review/SKILL.md +1 -1
- package/skills/finish/SKILL.md +1 -1
- package/skills/fix-review/SKILL.md +1 -1
- package/skills/go-style/SKILL.md +1 -1
- package/skills/kratos/SKILL.md +2 -1
- package/skills/plan-to-exec/SKILL.md +12 -1
- package/skills/refactor-plan/SKILL.md +1 -1
- package/skills/requirement-analyzer/SKILL.md +161 -0
- package/skills/requirement-analyzer/references/example-reports.md +170 -0
- package/skills/requirement-analyzer/references/prd-gap-checklist.md +167 -0
- package/skills/requirement-analyzer/references/readiness-rubric.md +70 -0
- package/skills/requirement-analyzer/references/report-template.md +83 -0
- package/skills/review/SKILL.md +1 -1
- package/skills/spec/SKILL.md +12 -1
- package/skills/sql-style/SKILL.md +108 -0
- package/skills/sql-style/references/database-design.md +402 -0
- package/skills/sql-style/references/dialect-differences.md +419 -0
- package/skills/sql-style/references/optimization.md +384 -0
- package/skills/sql-style/references/query-patterns.md +285 -0
- package/skills/sql-style/references/window-functions.md +328 -0
- package/skills/subagent-exec/SKILL.md +1 -1
- package/skills/tdd/SKILL.md +1 -1
- package/skills/verify/SKILL.md +1 -1
- package/src/cli.mjs +4 -1
- package/src/context-manifest.mjs +51 -1
- package/src/install-discovery.mjs +114 -0
- package/src/loopx-context-artifacts.mjs +114 -0
- package/src/project-discovery.mjs +1 -0
- 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)) {
|