worclaude 2.4.4 → 2.4.6
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/CHANGELOG.md +34 -0
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/commands/diff.js +12 -3
- package/src/commands/doctor.js +3 -19
- package/src/commands/init.js +1 -145
- package/src/commands/upgrade.js +356 -104
- package/src/core/config.js +8 -0
- package/src/core/drift-checks.js +63 -0
- package/src/core/file-categorizer.js +105 -37
- package/src/core/variables.js +196 -0
- package/src/index.js +4 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { fileExists, readFile, writeFile } from '../utils/file.js';
|
|
4
|
+
|
|
5
|
+
export const MEMORY_GUIDANCE_KEYWORDS = [
|
|
6
|
+
'memory architecture',
|
|
7
|
+
'native memory',
|
|
8
|
+
'.claude/learnings',
|
|
9
|
+
'[LEARN]',
|
|
10
|
+
'/learn',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export function hasClaudeMdMemoryGuidance(content) {
|
|
14
|
+
if (typeof content !== 'string' || content.length === 0) return false;
|
|
15
|
+
const lower = content.toLowerCase();
|
|
16
|
+
return MEMORY_GUIDANCE_KEYWORDS.some((kw) => lower.includes(kw.toLowerCase()));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function ensureLearningsDir(projectRoot) {
|
|
20
|
+
const dir = path.join(projectRoot, '.claude', 'learnings');
|
|
21
|
+
const gitkeep = path.join(dir, '.gitkeep');
|
|
22
|
+
if (await fs.pathExists(gitkeep)) return false;
|
|
23
|
+
await fs.ensureDir(dir);
|
|
24
|
+
await writeFile(gitkeep, '');
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function buildMemoryGuidanceSidecar() {
|
|
29
|
+
const preamble = [
|
|
30
|
+
'# CLAUDE.md — Memory Architecture Suggestion',
|
|
31
|
+
'',
|
|
32
|
+
'Your CLAUDE.md does not reference the workflow memory architecture. Auto-learnings may',
|
|
33
|
+
'pollute the main file. Paste the snippet below into CLAUDE.md (near the top, under an',
|
|
34
|
+
'existing "Conventions" or "Session Protocol" section), then delete this sidecar file.',
|
|
35
|
+
'',
|
|
36
|
+
'---',
|
|
37
|
+
'',
|
|
38
|
+
'## Memory Architecture',
|
|
39
|
+
'',
|
|
40
|
+
'- Auto-memory: `.claude/learnings/` (captured by hooks; reviewed via `/learn`).',
|
|
41
|
+
'- CLAUDE.md stays lean — it is shared with teammates. Long-form notes belong in',
|
|
42
|
+
' `docs/memory/` or the learnings directory.',
|
|
43
|
+
'- The `[LEARN]` marker in tool output flags moments worth capturing.',
|
|
44
|
+
'',
|
|
45
|
+
];
|
|
46
|
+
return preamble.join('\n');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function writeMemoryGuidanceSidecar(projectRoot) {
|
|
50
|
+
const dest = path.join(projectRoot, 'CLAUDE.md.workflow-ref.md');
|
|
51
|
+
await writeFile(dest, buildMemoryGuidanceSidecar());
|
|
52
|
+
return dest;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function readClaudeMd(projectRoot) {
|
|
56
|
+
const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
|
|
57
|
+
if (!(await fileExists(claudeMdPath))) return null;
|
|
58
|
+
try {
|
|
59
|
+
return await readFile(claudeMdPath);
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
+
import fs from 'fs-extra';
|
|
2
3
|
import { hashContent, hashFile } from '../utils/hash.js';
|
|
3
|
-
import { readTemplate } from './scaffolder.js';
|
|
4
|
+
import { readTemplate, getTemplatesDir } from './scaffolder.js';
|
|
4
5
|
import { fileExists, listFilesRecursive } from '../utils/file.js';
|
|
5
6
|
import {
|
|
6
7
|
UNIVERSAL_AGENTS,
|
|
@@ -10,6 +11,40 @@ import {
|
|
|
10
11
|
TEMPLATE_SKILLS,
|
|
11
12
|
} from '../data/agents.js';
|
|
12
13
|
|
|
14
|
+
const ALWAYS_SCAFFOLDED_TYPES = new Set([
|
|
15
|
+
'universal-agent',
|
|
16
|
+
'command',
|
|
17
|
+
'universal-skill',
|
|
18
|
+
'hook',
|
|
19
|
+
'root-file',
|
|
20
|
+
]);
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Predicate: is this template entry one that should be restored when missing on disk?
|
|
24
|
+
* - universal-agent, command, universal-skill, hook, root-file: always scaffolded
|
|
25
|
+
* - optional-agent: only if the agent name is in meta.optionalAgents
|
|
26
|
+
* - template-skill: excluded (needs variable substitution; stored hash won't match)
|
|
27
|
+
*/
|
|
28
|
+
export function isAlwaysScaffolded(entry, meta) {
|
|
29
|
+
if (!entry) return false;
|
|
30
|
+
if (ALWAYS_SCAFFOLDED_TYPES.has(entry.type)) return true;
|
|
31
|
+
if (entry.type === 'optional-agent') {
|
|
32
|
+
const agentName = entry.agentName;
|
|
33
|
+
return Boolean(agentName && meta?.optionalAgents?.includes(agentName));
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const ROOT_KEY_PREFIX = 'root/';
|
|
39
|
+
|
|
40
|
+
export function resolveKeyPath(key, projectRoot) {
|
|
41
|
+
if (key.startsWith(ROOT_KEY_PREFIX)) {
|
|
42
|
+
const rel = key.slice(ROOT_KEY_PREFIX.length);
|
|
43
|
+
return path.join(projectRoot, ...rel.split('/'));
|
|
44
|
+
}
|
|
45
|
+
return path.join(projectRoot, '.claude', ...key.split('/'));
|
|
46
|
+
}
|
|
47
|
+
|
|
13
48
|
/**
|
|
14
49
|
* Build a map of all workflow template files to their hash keys, template paths, and hashes.
|
|
15
50
|
* Hash keys match the format stored in workflow-meta.json (relative to .claude/).
|
|
@@ -30,7 +65,12 @@ export async function buildTemplateHashMap() {
|
|
|
30
65
|
const key = `agents/${name}.md`;
|
|
31
66
|
const templatePath = `agents/optional/${info.category}/${name}.md`;
|
|
32
67
|
const content = await readTemplate(templatePath);
|
|
33
|
-
map[key] = {
|
|
68
|
+
map[key] = {
|
|
69
|
+
templatePath,
|
|
70
|
+
hash: hashContent(content),
|
|
71
|
+
type: 'optional-agent',
|
|
72
|
+
agentName: name,
|
|
73
|
+
};
|
|
34
74
|
}
|
|
35
75
|
|
|
36
76
|
// Commands: key = commands/{name}.md, templatePath = commands/{name}.md
|
|
@@ -58,6 +98,34 @@ export async function buildTemplateHashMap() {
|
|
|
58
98
|
map[key] = { templatePath, hash: hashContent(content), type: 'template-skill' };
|
|
59
99
|
}
|
|
60
100
|
|
|
101
|
+
// Hook scripts: walked from templates/hooks/ so new hooks flow through automatically.
|
|
102
|
+
// Key = hooks/{name}, templatePath = hooks/{name}
|
|
103
|
+
const hooksDir = path.join(getTemplatesDir(), 'hooks');
|
|
104
|
+
if (await fs.pathExists(hooksDir)) {
|
|
105
|
+
const entries = await fs.readdir(hooksDir);
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
if (!entry.endsWith('.cjs') && !entry.endsWith('.js')) continue;
|
|
108
|
+
const key = `hooks/${entry}`;
|
|
109
|
+
const templatePath = `hooks/${entry}`;
|
|
110
|
+
const content = await readTemplate(templatePath);
|
|
111
|
+
map[key] = { templatePath, hash: hashContent(content), type: 'hook' };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Root-level files: key = root/<path>, templatePath points into templates/
|
|
116
|
+
// AGENTS.md needs variable substitution at scaffold time; the raw-template hash
|
|
117
|
+
// is used only to detect drift against a previously-substituted install hash,
|
|
118
|
+
// and a mismatch routes us down the user-modified path (no false overwrite).
|
|
119
|
+
{
|
|
120
|
+
const templatePath = 'core/agents-md.md';
|
|
121
|
+
const content = await readTemplate(templatePath);
|
|
122
|
+
map['root/AGENTS.md'] = {
|
|
123
|
+
templatePath,
|
|
124
|
+
hash: hashContent(content),
|
|
125
|
+
type: 'root-file',
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
61
129
|
return map;
|
|
62
130
|
}
|
|
63
131
|
|
|
@@ -74,7 +142,8 @@ export async function categorizeFiles(projectRoot, meta) {
|
|
|
74
142
|
conflict: [],
|
|
75
143
|
newFiles: [],
|
|
76
144
|
unchanged: [],
|
|
77
|
-
|
|
145
|
+
missingExpected: [],
|
|
146
|
+
missingUntracked: [],
|
|
78
147
|
userAdded: [],
|
|
79
148
|
modified: [],
|
|
80
149
|
outdated: [],
|
|
@@ -83,14 +152,31 @@ export async function categorizeFiles(projectRoot, meta) {
|
|
|
83
152
|
// Track which keys we've processed
|
|
84
153
|
const processedKeys = new Set();
|
|
85
154
|
|
|
155
|
+
// Shared routing for files whose outdated-detection step is skipped (no
|
|
156
|
+
// template entry, or template has variable placeholders we can't hash
|
|
157
|
+
// against). Classification reduces to "did the user touch it?"
|
|
158
|
+
const recordByUserModification = (key, userModified) => {
|
|
159
|
+
if (userModified) result.modified.push({ key });
|
|
160
|
+
else result.unchanged.push({ key });
|
|
161
|
+
};
|
|
162
|
+
|
|
86
163
|
// 1. Process each file in stored hashes
|
|
87
164
|
for (const [key, storedHash] of Object.entries(storedHashes)) {
|
|
88
165
|
processedKeys.add(key);
|
|
89
|
-
const filePath =
|
|
166
|
+
const filePath = resolveKeyPath(key, projectRoot);
|
|
90
167
|
|
|
91
168
|
// Check if file still exists on disk
|
|
92
169
|
if (!(await fileExists(filePath))) {
|
|
93
|
-
|
|
170
|
+
const templateEntry = templateMap[key];
|
|
171
|
+
if (isAlwaysScaffolded(templateEntry, meta)) {
|
|
172
|
+
result.missingExpected.push({
|
|
173
|
+
key,
|
|
174
|
+
templatePath: templateEntry.templatePath,
|
|
175
|
+
type: templateEntry.type,
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
result.missingUntracked.push({ key });
|
|
179
|
+
}
|
|
94
180
|
continue;
|
|
95
181
|
}
|
|
96
182
|
|
|
@@ -102,22 +188,17 @@ export async function categorizeFiles(projectRoot, meta) {
|
|
|
102
188
|
const templateEntry = templateMap[key];
|
|
103
189
|
|
|
104
190
|
if (!templateEntry) {
|
|
105
|
-
// File is in stored hashes but not in template map — treat as tracked file
|
|
106
|
-
|
|
107
|
-
result.modified.push({ key });
|
|
108
|
-
} else {
|
|
109
|
-
result.unchanged.push({ key });
|
|
110
|
-
}
|
|
191
|
+
// File is in stored hashes but not in template map — treat as tracked file.
|
|
192
|
+
recordByUserModification(key, userModified);
|
|
111
193
|
continue;
|
|
112
194
|
}
|
|
113
195
|
|
|
114
|
-
// Skip template skills from outdated detection
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
196
|
+
// Skip template skills and root-files from outdated detection —
|
|
197
|
+
// both contain {variable} placeholders, so raw-template hash won't
|
|
198
|
+
// match the installed (substituted) hash. Restoration paths still
|
|
199
|
+
// catch them when missing; we just can't auto-update them here.
|
|
200
|
+
if (templateEntry.type === 'template-skill' || templateEntry.type === 'root-file') {
|
|
201
|
+
recordByUserModification(key, userModified);
|
|
121
202
|
continue;
|
|
122
203
|
}
|
|
123
204
|
|
|
@@ -125,11 +206,7 @@ export async function categorizeFiles(projectRoot, meta) {
|
|
|
125
206
|
|
|
126
207
|
if (!templateChanged) {
|
|
127
208
|
// Template unchanged — only report user modifications
|
|
128
|
-
|
|
129
|
-
result.modified.push({ key });
|
|
130
|
-
} else {
|
|
131
|
-
result.unchanged.push({ key });
|
|
132
|
-
}
|
|
209
|
+
recordByUserModification(key, userModified);
|
|
133
210
|
} else {
|
|
134
211
|
// Template was updated in new version
|
|
135
212
|
result.outdated.push({ key, templatePath: templateEntry.templatePath });
|
|
@@ -141,22 +218,13 @@ export async function categorizeFiles(projectRoot, meta) {
|
|
|
141
218
|
}
|
|
142
219
|
}
|
|
143
220
|
|
|
144
|
-
// 2. Find new files (in template map but not in stored hashes)
|
|
221
|
+
// 2. Find new files (in template map but not in stored hashes).
|
|
222
|
+
// Gate is the same predicate used for missingExpected: always-scaffolded types,
|
|
223
|
+
// selected optional agents only, template skills excluded (need substitution).
|
|
145
224
|
for (const [key, entry] of Object.entries(templateMap)) {
|
|
146
225
|
if (processedKeys.has(key)) continue;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if (entry.type === 'optional-agent') {
|
|
150
|
-
const agentName = key.replace('agents/', '').replace('.md', '');
|
|
151
|
-
if (!meta.optionalAgents?.includes(agentName)) {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Skip template skills (would need variable substitution)
|
|
157
|
-
if (entry.type === 'template-skill') continue;
|
|
158
|
-
|
|
159
|
-
result.newFiles.push({ key, templatePath: entry.templatePath });
|
|
226
|
+
if (!isAlwaysScaffolded(entry, meta)) continue;
|
|
227
|
+
result.newFiles.push({ key, templatePath: entry.templatePath, type: entry.type });
|
|
160
228
|
}
|
|
161
229
|
|
|
162
230
|
// 3. Find user-added files (on disk but not in stored hashes or template map)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readFile, fileExists } from '../utils/file.js';
|
|
3
|
+
import { TECH_STACKS } from '../data/agents.js';
|
|
4
|
+
|
|
5
|
+
export const LANGUAGE_COMMANDS = {
|
|
6
|
+
python: {
|
|
7
|
+
heading: 'Python',
|
|
8
|
+
commands: [
|
|
9
|
+
'python -m pytest # Run tests',
|
|
10
|
+
'ruff check . # Lint',
|
|
11
|
+
'ruff format . # Format',
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
node: {
|
|
15
|
+
heading: 'Node.js / TypeScript',
|
|
16
|
+
commands: [
|
|
17
|
+
'npm test # Run tests',
|
|
18
|
+
'npx eslint . # Lint',
|
|
19
|
+
'npx prettier --write . # Format',
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
java: {
|
|
23
|
+
heading: 'Java',
|
|
24
|
+
commands: [
|
|
25
|
+
'mvn test # Run tests',
|
|
26
|
+
'mvn checkstyle:check # Lint',
|
|
27
|
+
'mvn spotless:apply # Format',
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
csharp: {
|
|
31
|
+
heading: 'C# / .NET',
|
|
32
|
+
commands: [
|
|
33
|
+
'dotnet test # Run tests',
|
|
34
|
+
'dotnet format --verify-no-changes # Lint',
|
|
35
|
+
'dotnet format # Format',
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
cpp: {
|
|
39
|
+
heading: 'C / C++',
|
|
40
|
+
commands: [
|
|
41
|
+
'cmake --build build && ctest # Build & test',
|
|
42
|
+
'clang-tidy src/*.cpp # Lint',
|
|
43
|
+
'clang-format -i src/*.[ch]pp # Format',
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
go: {
|
|
47
|
+
heading: 'Go',
|
|
48
|
+
commands: [
|
|
49
|
+
'go test ./... # Run tests',
|
|
50
|
+
'golangci-lint run # Lint',
|
|
51
|
+
'gofmt -w . # Format',
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
php: {
|
|
55
|
+
heading: 'PHP',
|
|
56
|
+
commands: [
|
|
57
|
+
'vendor/bin/phpunit # Run tests',
|
|
58
|
+
'vendor/bin/phpstan analyse # Lint',
|
|
59
|
+
'vendor/bin/php-cs-fixer fix . # Format',
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
ruby: {
|
|
63
|
+
heading: 'Ruby',
|
|
64
|
+
commands: [
|
|
65
|
+
'bundle exec rspec # Run tests',
|
|
66
|
+
'rubocop # Lint',
|
|
67
|
+
'rubocop -A # Format',
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
kotlin: {
|
|
71
|
+
heading: 'Kotlin',
|
|
72
|
+
commands: [
|
|
73
|
+
'gradle test # Run tests',
|
|
74
|
+
'detekt # Lint',
|
|
75
|
+
'ktlint -F # Format',
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
swift: {
|
|
79
|
+
heading: 'Swift',
|
|
80
|
+
commands: [
|
|
81
|
+
'swift test # Run tests',
|
|
82
|
+
'swiftlint # Lint',
|
|
83
|
+
'swift-format format -r . -i # Format',
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
rust: {
|
|
87
|
+
heading: 'Rust',
|
|
88
|
+
commands: [
|
|
89
|
+
'cargo test # Run tests',
|
|
90
|
+
'cargo clippy # Lint',
|
|
91
|
+
'cargo fmt # Format',
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
dart: {
|
|
95
|
+
heading: 'Dart / Flutter',
|
|
96
|
+
commands: [
|
|
97
|
+
'dart test # Run tests',
|
|
98
|
+
'dart analyze # Lint',
|
|
99
|
+
'dart format . # Format',
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
scala: {
|
|
103
|
+
heading: 'Scala',
|
|
104
|
+
commands: [
|
|
105
|
+
'sbt test # Run tests',
|
|
106
|
+
'sbt scalafix # Lint',
|
|
107
|
+
'scalafmt # Format',
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
elixir: {
|
|
111
|
+
heading: 'Elixir',
|
|
112
|
+
commands: [
|
|
113
|
+
'mix test # Run tests',
|
|
114
|
+
'mix credo # Lint',
|
|
115
|
+
'mix format # Format',
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
zig: {
|
|
119
|
+
heading: 'Zig',
|
|
120
|
+
commands: [
|
|
121
|
+
'zig build test # Run tests',
|
|
122
|
+
'zig build # Build (lint via compiler)',
|
|
123
|
+
'zig fmt . # Format',
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export function buildCommandsBlock(languages, useDocker) {
|
|
129
|
+
const lines = ['```bash'];
|
|
130
|
+
for (const lang of languages) {
|
|
131
|
+
const entry = LANGUAGE_COMMANDS[lang];
|
|
132
|
+
if (!entry) continue;
|
|
133
|
+
if (lines.length > 1) lines.push('');
|
|
134
|
+
lines.push(`# ${entry.heading}`);
|
|
135
|
+
lines.push(...entry.commands);
|
|
136
|
+
}
|
|
137
|
+
if (useDocker) {
|
|
138
|
+
if (lines.length > 1) lines.push('');
|
|
139
|
+
lines.push('# Docker');
|
|
140
|
+
lines.push('docker compose up -d # Start services');
|
|
141
|
+
lines.push('docker compose down # Stop services');
|
|
142
|
+
}
|
|
143
|
+
if (lines.length === 1) {
|
|
144
|
+
lines.push('# Add your project-specific commands here');
|
|
145
|
+
}
|
|
146
|
+
lines.push('```');
|
|
147
|
+
return lines.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function buildTechStackText(languages, useDocker) {
|
|
151
|
+
const lines = languages
|
|
152
|
+
.filter((l) => l !== 'other')
|
|
153
|
+
.map((l) => {
|
|
154
|
+
const entry = TECH_STACKS.find((s) => s.value === l);
|
|
155
|
+
return `- ${entry ? entry.name : l}`;
|
|
156
|
+
});
|
|
157
|
+
if (languages.includes('other') && lines.length === 0) {
|
|
158
|
+
lines.push('- Not specified');
|
|
159
|
+
}
|
|
160
|
+
if (useDocker) lines.push('- Docker');
|
|
161
|
+
return lines.join('\n');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function readPackageJsonFields(projectRoot) {
|
|
165
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
166
|
+
if (!(await fileExists(pkgPath))) return {};
|
|
167
|
+
try {
|
|
168
|
+
const content = await readFile(pkgPath);
|
|
169
|
+
const pkg = JSON.parse(content);
|
|
170
|
+
return { name: pkg.name, description: pkg.description };
|
|
171
|
+
} catch {
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Reconstruct template variables from workflow-meta.json for repair flows.
|
|
178
|
+
* Best-effort: pulls projectName/description from package.json, tech stack
|
|
179
|
+
* from meta.techStack, commands block from meta.techStack + meta.useDocker.
|
|
180
|
+
*/
|
|
181
|
+
export async function buildAgentsMdVariables(meta, projectRoot) {
|
|
182
|
+
const languages = meta.techStack || [];
|
|
183
|
+
const useDocker = meta.useDocker || false;
|
|
184
|
+
const pkg = await readPackageJsonFields(projectRoot);
|
|
185
|
+
const projectName = pkg.name || path.basename(projectRoot);
|
|
186
|
+
const description = pkg.description || 'A project scaffolded with Worclaude';
|
|
187
|
+
const techStackText = buildTechStackText(languages, useDocker);
|
|
188
|
+
const commandsText = buildCommandsBlock(languages, useDocker);
|
|
189
|
+
return {
|
|
190
|
+
project_name: projectName,
|
|
191
|
+
description,
|
|
192
|
+
tech_stack_filled_during_init: techStackText,
|
|
193
|
+
commands_filled_during_init: commandsText,
|
|
194
|
+
timestamp: new Date().toISOString(),
|
|
195
|
+
};
|
|
196
|
+
}
|
package/src/index.js
CHANGED
|
@@ -29,7 +29,10 @@ program
|
|
|
29
29
|
program
|
|
30
30
|
.command('upgrade')
|
|
31
31
|
.description('Update workflow components to the latest version')
|
|
32
|
-
.
|
|
32
|
+
.option('--dry-run', 'Preview changes without writing')
|
|
33
|
+
.option('--yes', 'Skip confirmation prompts')
|
|
34
|
+
.option('--repair-only', 'Restore missing files without applying template updates')
|
|
35
|
+
.action((options) => upgradeCommand(options));
|
|
33
36
|
|
|
34
37
|
program
|
|
35
38
|
.command('status')
|