@ai-content-space/loopx 0.1.2 → 0.1.4

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 (69) hide show
  1. package/README.md +422 -57
  2. package/README.zh-CN.md +485 -0
  3. package/assets/logo.svg +89 -0
  4. package/package.json +5 -1
  5. package/plugins/loopx/.codex-plugin/plugin.json +1 -1
  6. package/plugins/loopx/scripts/plugin-install.test.mjs +14 -0
  7. package/plugins/loopx/skills/archive/SKILL.md +49 -0
  8. package/plugins/loopx/skills/build/SKILL.md +111 -9
  9. package/plugins/loopx/skills/clarify/SKILL.md +129 -8
  10. package/plugins/loopx/skills/debug/SKILL.md +296 -0
  11. package/plugins/loopx/skills/debug/condition-based-waiting.md +115 -0
  12. package/plugins/loopx/skills/debug/defense-in-depth.md +122 -0
  13. package/plugins/loopx/skills/debug/find-polluter.sh +63 -0
  14. package/plugins/loopx/skills/debug/root-cause-tracing.md +169 -0
  15. package/plugins/loopx/skills/go-style/SKILL.md +71 -0
  16. package/plugins/loopx/skills/kratos/SKILL.md +74 -0
  17. package/plugins/loopx/skills/kratos/references/advanced-features.md +314 -0
  18. package/plugins/loopx/skills/kratos/references/architecture.md +488 -0
  19. package/plugins/loopx/skills/kratos/references/configuration.md +399 -0
  20. package/plugins/loopx/skills/kratos/references/http-customization.md +512 -0
  21. package/plugins/loopx/skills/kratos/references/middleware-logging.md +400 -0
  22. package/plugins/loopx/skills/kratos/references/proto-api-design.md +432 -0
  23. package/plugins/loopx/skills/kratos/references/security-auth.md +411 -0
  24. package/plugins/loopx/skills/kratos/references/troubleshooting.md +385 -0
  25. package/plugins/loopx/skills/plan/SKILL.md +24 -3
  26. package/plugins/loopx/skills/review/SKILL.md +98 -1
  27. package/plugins/loopx/skills/tdd/SKILL.md +371 -0
  28. package/plugins/loopx/skills/tdd/testing-anti-patterns.md +299 -0
  29. package/plugins/loopx/skills/verify/SKILL.md +139 -0
  30. package/scripts/codex-stop-hook.mjs +71 -0
  31. package/scripts/codex-workflow-hook.mjs +248 -0
  32. package/skills/archive/SKILL.md +49 -0
  33. package/skills/build/SKILL.md +111 -9
  34. package/skills/clarify/SKILL.md +129 -8
  35. package/skills/debug/SKILL.md +296 -0
  36. package/skills/debug/condition-based-waiting.md +115 -0
  37. package/skills/debug/defense-in-depth.md +122 -0
  38. package/skills/debug/find-polluter.sh +63 -0
  39. package/skills/debug/root-cause-tracing.md +169 -0
  40. package/skills/go-style/SKILL.md +71 -0
  41. package/skills/kratos/SKILL.md +74 -0
  42. package/skills/kratos/references/advanced-features.md +314 -0
  43. package/skills/kratos/references/architecture.md +488 -0
  44. package/skills/kratos/references/configuration.md +399 -0
  45. package/skills/kratos/references/http-customization.md +512 -0
  46. package/skills/kratos/references/middleware-logging.md +400 -0
  47. package/skills/kratos/references/proto-api-design.md +432 -0
  48. package/skills/kratos/references/security-auth.md +411 -0
  49. package/skills/kratos/references/troubleshooting.md +385 -0
  50. package/skills/plan/SKILL.md +20 -3
  51. package/skills/review/SKILL.md +98 -1
  52. package/skills/tdd/SKILL.md +371 -0
  53. package/skills/tdd/testing-anti-patterns.md +299 -0
  54. package/skills/verify/SKILL.md +139 -0
  55. package/src/build-runtime.mjs +311 -26
  56. package/src/build-stop-gate.mjs +94 -0
  57. package/src/cli.mjs +57 -5
  58. package/src/codex-exec-runtime.mjs +105 -5
  59. package/src/context-manifest.mjs +172 -0
  60. package/src/html-views.mjs +316 -0
  61. package/src/install-discovery.mjs +352 -5
  62. package/src/next-skill.mjs +57 -5
  63. package/src/plan-runtime.mjs +102 -122
  64. package/src/review-runtime.mjs +558 -0
  65. package/src/runtime-maintenance.mjs +429 -14
  66. package/src/template-governance.mjs +223 -0
  67. package/src/workflow.mjs +2341 -120
  68. package/src/workspace-context.mjs +166 -0
  69. package/src/workspace-memory.mjs +69 -0
@@ -0,0 +1,166 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
+ import { dirname, join, resolve } from 'node:path';
4
+
5
+ export function resolveWorkspaceContextPaths(cwd) {
6
+ const workspaceRoot = join(resolve(cwd), '.loopx');
7
+ const agentsRoot = join(workspaceRoot, 'agents');
8
+ const contextRoot = join(workspaceRoot, 'context');
9
+ return {
10
+ workspaceRoot,
11
+ agentsRoot,
12
+ contextRoot,
13
+ issueTracker: join(agentsRoot, 'issue-tracker.md'),
14
+ agentDomain: join(agentsRoot, 'domain.md'),
15
+ triageLabels: join(agentsRoot, 'triage-labels.md'),
16
+ domainGlossary: join(contextRoot, 'domain.md'),
17
+ };
18
+ }
19
+
20
+ async function writeIfMissing(path, text) {
21
+ if (existsSync(path)) {
22
+ return false;
23
+ }
24
+ await mkdir(dirname(path), { recursive: true });
25
+ await writeFile(path, `${text.replace(/\s+$/, '')}\n`);
26
+ return true;
27
+ }
28
+
29
+ function defaultIssueTrackerDoc() {
30
+ return [
31
+ '# loopx Agent Issue Tracker',
32
+ '',
33
+ '## Mode',
34
+ '',
35
+ '- local: loopx does not publish external issues by default.',
36
+ '',
37
+ '## Usage',
38
+ '',
39
+ '- Plan may create vertical slices as local change artifacts.',
40
+ '- External issue publication is an explicit human decision.',
41
+ ].join('\n');
42
+ }
43
+
44
+ function defaultAgentDomainDoc() {
45
+ return [
46
+ '# loopx Agent Domain Docs',
47
+ '',
48
+ '## Layout',
49
+ '',
50
+ '- domain_glossary: `.loopx/context/domain.md`',
51
+ '- adr_candidates: `.loopx/decisions/adr-candidates/`',
52
+ '',
53
+ '## Consumer Rules',
54
+ '',
55
+ '- Plan uses the glossary to name requirements, slices, and spec deltas.',
56
+ '- Build uses the glossary to preserve implementation vocabulary.',
57
+ '- Review uses the glossary to detect terminology drift and architecture smells.',
58
+ ].join('\n');
59
+ }
60
+
61
+ function defaultTriageLabelsDoc() {
62
+ return [
63
+ '# loopx Agent Triage Labels',
64
+ '',
65
+ '## Canonical Roles',
66
+ '',
67
+ '- needs-triage',
68
+ '- needs-info',
69
+ '- ready-for-agent',
70
+ '- ready-for-human',
71
+ '- wontfix',
72
+ '',
73
+ '## Mapping',
74
+ '',
75
+ '- loopx keeps these as local advisory roles unless an external tracker is configured.',
76
+ ].join('\n');
77
+ }
78
+
79
+ function defaultDomainGlossaryDoc() {
80
+ return [
81
+ '# loopx Domain Context',
82
+ '',
83
+ '## Canonical Terms',
84
+ '',
85
+ '- Change Delta: 已批准变更进入长期 specs 前的差量描述。',
86
+ '- Vertical Slice: 可独立验证的端到端交付切片。',
87
+ '',
88
+ '## Avoid Terms',
89
+ '',
90
+ '- Ticket: use workflow, change, or slice unless referencing an external issue tracker.',
91
+ '',
92
+ '## Ambiguous Terms',
93
+ '',
94
+ '- Done: clarify whether this means review-approved runtime state or archived long-lived specs.',
95
+ '',
96
+ '## Relationships',
97
+ '',
98
+ '- A workflow owns one active change delta.',
99
+ '- A change delta may contain multiple vertical slices.',
100
+ '- Archive syncs accepted change deltas into long-lived specs.',
101
+ ].join('\n');
102
+ }
103
+
104
+ function fileInfo(path) {
105
+ return {
106
+ path,
107
+ exists: existsSync(path),
108
+ };
109
+ }
110
+
111
+ export async function setupWorkspaceContext(cwd) {
112
+ const paths = resolveWorkspaceContextPaths(cwd);
113
+ await mkdir(paths.agentsRoot, { recursive: true });
114
+ await mkdir(paths.contextRoot, { recursive: true });
115
+ const created = [];
116
+ for (const [path, text] of [
117
+ [paths.issueTracker, defaultIssueTrackerDoc()],
118
+ [paths.agentDomain, defaultAgentDomainDoc()],
119
+ [paths.triageLabels, defaultTriageLabelsDoc()],
120
+ [paths.domainGlossary, defaultDomainGlossaryDoc()],
121
+ ]) {
122
+ if (await writeIfMissing(path, text)) {
123
+ created.push(path);
124
+ }
125
+ }
126
+ return {
127
+ status: 'complete',
128
+ created,
129
+ ...await inspectWorkspaceContext(cwd),
130
+ };
131
+ }
132
+
133
+ export async function inspectWorkspaceContext(cwd) {
134
+ const paths = resolveWorkspaceContextPaths(cwd);
135
+ const agentDocs = {
136
+ issueTracker: fileInfo(paths.issueTracker),
137
+ domain: fileInfo(paths.agentDomain),
138
+ triageLabels: fileInfo(paths.triageLabels),
139
+ };
140
+ const domainGlossary = fileInfo(paths.domainGlossary);
141
+ const all = [agentDocs.issueTracker, agentDocs.domain, agentDocs.triageLabels, domainGlossary];
142
+ const existing = all.filter((item) => item.exists);
143
+ let status = 'missing';
144
+ if (existing.length === all.length) {
145
+ status = 'complete';
146
+ } else if (existing.length > 0) {
147
+ status = 'partial';
148
+ }
149
+ return {
150
+ status,
151
+ workspaceRoot: paths.workspaceRoot,
152
+ agentsRoot: paths.agentsRoot,
153
+ contextRoot: paths.contextRoot,
154
+ agentDocs,
155
+ domainGlossaryPath: paths.domainGlossary,
156
+ domainGlossary,
157
+ };
158
+ }
159
+
160
+ export async function readDomainGlossary(cwd) {
161
+ const { domainGlossary } = resolveWorkspaceContextPaths(cwd);
162
+ if (!existsSync(domainGlossary)) {
163
+ return '';
164
+ }
165
+ return readFile(domainGlossary, 'utf8');
166
+ }
@@ -0,0 +1,69 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { mkdir, readdir, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { promisify } from 'node:util';
5
+
6
+ const execFileAsync = promisify(execFile);
7
+
8
+ async function gitConfig(cwd, key) {
9
+ try {
10
+ const { stdout } = await execFileAsync('git', ['config', key], { cwd });
11
+ return stdout.trim() || null;
12
+ } catch {
13
+ return null;
14
+ }
15
+ }
16
+
17
+ export async function resolveDeveloperIdentity(cwd, env = process.env) {
18
+ return env.LOOPX_DEVELOPER
19
+ || await gitConfig(cwd, 'user.email')
20
+ || await gitConfig(cwd, 'user.name')
21
+ || env.USER
22
+ || 'unknown-developer';
23
+ }
24
+
25
+ export function sanitizeDeveloperIdentity(identity) {
26
+ return String(identity || 'unknown-developer')
27
+ .trim()
28
+ .replace(/[^a-zA-Z0-9._@-]+/g, '-')
29
+ .replace(/^-+|-+$/g, '')
30
+ || 'unknown-developer';
31
+ }
32
+
33
+ export async function appendWorkspaceJournal({ cwd, workspaceRoot, slug, stage = 'review', verdict, reviewMessageZh, verificationEvidence = [], decisions = [], risks = [], followUps = [], env = process.env }) {
34
+ const developer = sanitizeDeveloperIdentity(await resolveDeveloperIdentity(cwd, env));
35
+ const root = join(workspaceRoot, 'workspace', developer);
36
+ await mkdir(root, { recursive: true });
37
+ const entries = await readdir(root, { withFileTypes: true });
38
+ const existingNumbers = entries
39
+ .filter((entry) => entry.isFile())
40
+ .map((entry) => /^journal-(\d+)\.md$/.exec(entry.name))
41
+ .filter(Boolean)
42
+ .map((match) => Number(match[1]))
43
+ .filter((value) => Number.isInteger(value) && value > 0);
44
+ const journalNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1;
45
+ const journalPath = join(root, `journal-${journalNumber}.md`);
46
+ const indexPath = join(root, 'index.md');
47
+ const entry = [
48
+ '# loopx Workspace Journal',
49
+ '',
50
+ `## ${slug} - ${new Date().toISOString()}`,
51
+ '',
52
+ `- 阶段:${stage}`,
53
+ `- 结论:${verdict}`,
54
+ `- 摘要:${reviewMessageZh || '无'}`,
55
+ '- 关键决策:',
56
+ ...(decisions.length > 0 ? decisions : ['保持 loopx 硬门控为权威状态。']).map((item) => ` - ${item}`),
57
+ '- 验证命令:',
58
+ ...(verificationEvidence.length > 0 ? verificationEvidence : ['见 execution-record.md']).map((item) => ` - ${item}`),
59
+ '- 残余风险:',
60
+ ...(risks.length > 0 ? risks : ['暂无新增残余风险。']).map((item) => ` - ${item}`),
61
+ '- 后续项:',
62
+ ...(followUps.length > 0 ? followUps : ['按 review 审批结果推进。']).map((item) => ` - ${item}`),
63
+ '',
64
+ ].join('\n');
65
+ await writeFile(journalPath, entry);
66
+ const nextIndex = [...existingNumbers, journalNumber].sort((left, right) => left - right);
67
+ await writeFile(indexPath, `# loopx Workspace Journal Index\n\n${nextIndex.map((number) => `- [journal-${number}.md](journal-${number}.md)`).join('\n')}\n`);
68
+ return { developer, journalPath, indexPath };
69
+ }