helloloop 0.2.0 → 0.3.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.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +228 -279
- package/hosts/claude/marketplace/.claude-plugin/marketplace.json +3 -1
- package/hosts/claude/marketplace/plugins/helloloop/.claude-plugin/plugin.json +1 -1
- package/hosts/claude/marketplace/plugins/helloloop/commands/helloloop.md +16 -9
- package/hosts/claude/marketplace/plugins/helloloop/skills/helloloop/SKILL.md +4 -1
- package/hosts/gemini/extension/GEMINI.md +8 -4
- package/hosts/gemini/extension/commands/helloloop.toml +15 -8
- package/hosts/gemini/extension/gemini-extension.json +1 -1
- package/package.json +2 -2
- package/scripts/uninstall-home-plugin.ps1 +25 -0
- package/skills/helloloop/SKILL.md +28 -3
- package/src/analyze_confirmation.mjs +79 -3
- package/src/analyze_prompt.mjs +12 -0
- package/src/analyze_user_input.mjs +303 -0
- package/src/analyzer.mjs +38 -0
- package/src/cli.mjs +211 -25
- package/src/cli_support.mjs +114 -27
- package/src/discovery.mjs +243 -9
- package/src/discovery_inference.mjs +62 -18
- package/src/discovery_paths.mjs +143 -8
- package/src/discovery_prompt.mjs +298 -0
- package/src/install.mjs +218 -7
- package/src/rebuild.mjs +116 -0
- package/templates/analysis-output.schema.json +58 -0
package/src/rebuild.mjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { ensureDir, nowIso, writeJson } from "./common.mjs";
|
|
5
|
+
|
|
6
|
+
const PROTECTED_TOP_LEVEL = new Set([
|
|
7
|
+
".git",
|
|
8
|
+
".gitignore",
|
|
9
|
+
".gitattributes",
|
|
10
|
+
".helloagents",
|
|
11
|
+
".helloloop",
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
function ensureSafeRepoRoot(repoRoot) {
|
|
15
|
+
const absoluteRepoRoot = path.resolve(repoRoot);
|
|
16
|
+
const parsed = path.parse(absoluteRepoRoot);
|
|
17
|
+
if (absoluteRepoRoot === parsed.root) {
|
|
18
|
+
throw new Error(`拒绝清理根目录:${absoluteRepoRoot}`);
|
|
19
|
+
}
|
|
20
|
+
if (!fs.existsSync(absoluteRepoRoot) || !fs.statSync(absoluteRepoRoot).isDirectory()) {
|
|
21
|
+
throw new Error(`项目目录不存在或不是目录:${absoluteRepoRoot}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function collectDocsInsideRepo(repoRoot, discovery) {
|
|
26
|
+
const resolvedDocs = Array.isArray(discovery?.resolvedDocs) ? discovery.resolvedDocs : [];
|
|
27
|
+
return resolvedDocs
|
|
28
|
+
.map((item) => ({
|
|
29
|
+
absolutePath: path.resolve(item.absolutePath),
|
|
30
|
+
relativePath: String(item.relativePath || "").replaceAll("\\", "/"),
|
|
31
|
+
}))
|
|
32
|
+
.filter((item) => {
|
|
33
|
+
const relative = path.relative(repoRoot, item.absolutePath);
|
|
34
|
+
return relative && !relative.startsWith("..") && !path.isAbsolute(relative);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function stagePreservedDocs(configRoot, docs) {
|
|
39
|
+
const stageRoot = path.join(configRoot, "rebuild-staging", nowIso().replaceAll(":", "-").replaceAll(".", "-"));
|
|
40
|
+
for (const doc of docs) {
|
|
41
|
+
const stagedTarget = path.join(stageRoot, doc.relativePath);
|
|
42
|
+
ensureDir(path.dirname(stagedTarget));
|
|
43
|
+
fs.copyFileSync(doc.absolutePath, stagedTarget);
|
|
44
|
+
}
|
|
45
|
+
return stageRoot;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function removeUnprotectedTopLevel(repoRoot) {
|
|
49
|
+
const removedEntries = [];
|
|
50
|
+
for (const entry of fs.readdirSync(repoRoot, { withFileTypes: true })) {
|
|
51
|
+
if (PROTECTED_TOP_LEVEL.has(entry.name)) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const entryPath = path.join(repoRoot, entry.name);
|
|
56
|
+
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
57
|
+
removedEntries.push(entry.name);
|
|
58
|
+
}
|
|
59
|
+
return removedEntries;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function restoreDocsFromStage(repoRoot, stageRoot, docs) {
|
|
63
|
+
for (const doc of docs) {
|
|
64
|
+
const stagedSource = path.join(stageRoot, doc.relativePath);
|
|
65
|
+
const targetPath = path.join(repoRoot, doc.relativePath);
|
|
66
|
+
ensureDir(path.dirname(targetPath));
|
|
67
|
+
fs.copyFileSync(stagedSource, targetPath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function resetLoopRuntime(context) {
|
|
72
|
+
for (const target of [
|
|
73
|
+
context.backlogFile,
|
|
74
|
+
context.projectFile,
|
|
75
|
+
context.statusFile,
|
|
76
|
+
context.stateFile,
|
|
77
|
+
]) {
|
|
78
|
+
if (fs.existsSync(target)) {
|
|
79
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (fs.existsSync(context.runsDir)) {
|
|
84
|
+
fs.rmSync(context.runsDir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function resetRepoForRebuild(context, discovery) {
|
|
89
|
+
ensureSafeRepoRoot(context.repoRoot);
|
|
90
|
+
|
|
91
|
+
const preservedDocs = collectDocsInsideRepo(context.repoRoot, discovery);
|
|
92
|
+
const stageRoot = preservedDocs.length
|
|
93
|
+
? stagePreservedDocs(context.configRoot, preservedDocs)
|
|
94
|
+
: "";
|
|
95
|
+
|
|
96
|
+
const manifestFile = path.join(context.configRoot, "rebuild-manifest.json");
|
|
97
|
+
const removedEntries = removeUnprotectedTopLevel(context.repoRoot);
|
|
98
|
+
resetLoopRuntime(context);
|
|
99
|
+
|
|
100
|
+
if (stageRoot) {
|
|
101
|
+
restoreDocsFromStage(context.repoRoot, stageRoot, preservedDocs);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
writeJson(manifestFile, {
|
|
105
|
+
updatedAt: nowIso(),
|
|
106
|
+
repoRoot: context.repoRoot,
|
|
107
|
+
removedEntries,
|
|
108
|
+
preservedDocs: preservedDocs.map((item) => item.relativePath),
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
removedEntries,
|
|
113
|
+
preservedDocs: preservedDocs.map((item) => item.relativePath),
|
|
114
|
+
manifestFile,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -50,6 +50,64 @@
|
|
|
50
50
|
"type": "string"
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
|
+
"requestInterpretation": {
|
|
54
|
+
"type": "object",
|
|
55
|
+
"additionalProperties": false,
|
|
56
|
+
"required": [
|
|
57
|
+
"summary",
|
|
58
|
+
"priorities",
|
|
59
|
+
"cautions"
|
|
60
|
+
],
|
|
61
|
+
"properties": {
|
|
62
|
+
"summary": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"minLength": 1
|
|
65
|
+
},
|
|
66
|
+
"priorities": {
|
|
67
|
+
"type": "array",
|
|
68
|
+
"items": {
|
|
69
|
+
"type": "string"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"cautions": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "string"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"repoDecision": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"additionalProperties": false,
|
|
83
|
+
"required": [
|
|
84
|
+
"compatibility",
|
|
85
|
+
"action",
|
|
86
|
+
"reason"
|
|
87
|
+
],
|
|
88
|
+
"properties": {
|
|
89
|
+
"compatibility": {
|
|
90
|
+
"type": "string",
|
|
91
|
+
"enum": [
|
|
92
|
+
"compatible",
|
|
93
|
+
"conflict",
|
|
94
|
+
"uncertain"
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
"action": {
|
|
98
|
+
"type": "string",
|
|
99
|
+
"enum": [
|
|
100
|
+
"continue_existing",
|
|
101
|
+
"confirm_rebuild",
|
|
102
|
+
"start_new"
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"reason": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"minLength": 1
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
53
111
|
"tasks": {
|
|
54
112
|
"type": "array",
|
|
55
113
|
"minItems": 1,
|