ace-pack 0.1.1 → 0.1.2
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 +40 -14
- package/install-ace-pack.mjs +252 -11
- package/install-agent-memory-pack.mjs +8 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -100,33 +100,62 @@ applied.
|
|
|
100
100
|
|
|
101
101
|
## Quick Start
|
|
102
102
|
|
|
103
|
-
Install ACE into the current repository
|
|
103
|
+
Install ACE into the current repository. Use `init`; do not use
|
|
104
|
+
`npm install ace-pack` for project setup.
|
|
104
105
|
|
|
105
106
|
```bash
|
|
106
|
-
|
|
107
|
+
npx ace-pack@latest init
|
|
107
108
|
```
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
Then profile the project:
|
|
110
111
|
|
|
111
112
|
```bash
|
|
112
|
-
|
|
113
|
+
npm run ace:onboard -- --apply
|
|
114
|
+
npm run ace:check
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Prefer pnpm? Use the same flow through `pnpm dlx`:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
pnpm dlx ace-pack init
|
|
121
|
+
pnpm ace:onboard -- --apply
|
|
122
|
+
pnpm ace:check
|
|
113
123
|
```
|
|
114
124
|
|
|
115
125
|
Install into another repository:
|
|
116
126
|
|
|
117
127
|
```bash
|
|
118
|
-
|
|
128
|
+
npx ace-pack@latest init ./my-project
|
|
119
129
|
```
|
|
120
130
|
|
|
121
|
-
|
|
131
|
+
Install and apply onboarding in one command:
|
|
122
132
|
|
|
123
133
|
```bash
|
|
124
|
-
|
|
125
|
-
pnpm ace:onboard -- --apply
|
|
134
|
+
npx ace-pack@latest init --apply
|
|
126
135
|
```
|
|
127
136
|
|
|
137
|
+
Need help?
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npx ace-pack@latest --help
|
|
141
|
+
npx ace-pack@latest init --help
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## What Init Does
|
|
145
|
+
|
|
146
|
+
`ace-pack init` adds or updates local project files:
|
|
147
|
+
|
|
148
|
+
- `AGENTS.md` and `CLAUDE.md`
|
|
149
|
+
- `.ai/*` memory, task, handoff, decisions, and profile files
|
|
150
|
+
- `scripts/*` ACE automation copied into the project
|
|
151
|
+
- `package.json` commands such as `ace:onboard`, `ace:classify`,
|
|
152
|
+
`ace:validate`, `ace:finish`, and `ace:hub`
|
|
153
|
+
|
|
154
|
+
ACE does not need to remain installed as a runtime dependency. The npm package
|
|
155
|
+
acts as a scaffold CLI, then the project owns the copied scripts.
|
|
156
|
+
|
|
128
157
|
For Python, Go, Rust, .NET, or any repo without `package.json`, ACE creates a
|
|
129
|
-
lightweight private runner package:
|
|
158
|
+
lightweight private runner package so the same commands are available:
|
|
130
159
|
|
|
131
160
|
```json
|
|
132
161
|
{
|
|
@@ -134,16 +163,13 @@ lightweight private runner package:
|
|
|
134
163
|
}
|
|
135
164
|
```
|
|
136
165
|
|
|
137
|
-
No dependency install is required for ACE itself. The runner only exposes
|
|
138
|
-
commands such as `ace:onboard`, `ace:hub`, `ace:classify`, and `ace:finish`.
|
|
139
|
-
|
|
140
166
|
On Windows PowerShell, use `pnpm.cmd` if script execution policy blocks the
|
|
141
167
|
regular `pnpm` shim:
|
|
142
168
|
|
|
143
169
|
```bash
|
|
170
|
+
pnpm.cmd dlx ace-pack init
|
|
144
171
|
pnpm.cmd ace:onboard -- --apply
|
|
145
|
-
pnpm.cmd ace:
|
|
146
|
-
pnpm.cmd ace:validate
|
|
172
|
+
pnpm.cmd ace:check
|
|
147
173
|
```
|
|
148
174
|
|
|
149
175
|
Known SaaS monorepo? Apply the built-in preset:
|
package/install-ace-pack.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { access, copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
|
|
6
|
+
import { onboardRepository } from './scripts/ace-onboard.mjs'
|
|
6
7
|
import { ensureAgentMemory } from './scripts/agent-memory-lib.mjs'
|
|
7
8
|
|
|
8
9
|
const REQUIRED_PACKAGE_SCRIPTS = {
|
|
@@ -65,6 +66,7 @@ const currentFilePath = fileURLToPath(import.meta.url)
|
|
|
65
66
|
const currentScriptDir = path.join(path.dirname(currentFilePath), 'scripts')
|
|
66
67
|
const RUNNER_PACKAGE_DESCRIPTION =
|
|
67
68
|
'Auto-generated lightweight runner for ACE (Agentic Context Engine) scripts. No node_modules required.'
|
|
69
|
+
const KNOWN_PACKAGE_MANAGERS = new Set(['npm', 'pnpm', 'yarn', 'bun'])
|
|
68
70
|
|
|
69
71
|
function normalizeTrailingNewline(content) {
|
|
70
72
|
return content.endsWith('\n') ? content : `${content}\n`
|
|
@@ -82,6 +84,19 @@ async function readTextIfExists(filePath) {
|
|
|
82
84
|
}
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
async function fileExists(filePath) {
|
|
88
|
+
try {
|
|
89
|
+
await access(filePath)
|
|
90
|
+
return true
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
throw error
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
85
100
|
async function ensureDefaultAgentsFile(rootDir) {
|
|
86
101
|
const agentsPath = path.join(rootDir, 'AGENTS.md')
|
|
87
102
|
const existingContent = await readTextIfExists(agentsPath)
|
|
@@ -231,24 +246,202 @@ export async function installAcePack(targetDir) {
|
|
|
231
246
|
createdFiles.push(...memoryResult.createdFiles)
|
|
232
247
|
updatedFiles.push(...memoryResult.updatedFiles)
|
|
233
248
|
|
|
249
|
+
const createdFileSet = new Set(createdFiles)
|
|
250
|
+
|
|
234
251
|
return {
|
|
235
252
|
createdFiles,
|
|
236
253
|
targetDir: normalizedTargetDir,
|
|
237
|
-
updatedFiles,
|
|
254
|
+
updatedFiles: updatedFiles.filter((filePath) => !createdFileSet.has(filePath)),
|
|
238
255
|
}
|
|
239
256
|
}
|
|
240
257
|
|
|
241
258
|
export function resolveTargetDir(args, cwd = process.cwd()) {
|
|
242
|
-
|
|
259
|
+
return parseInstallArgs(args, cwd).targetDir
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function parseInstallArgs(args, cwd = process.cwd()) {
|
|
263
|
+
if (args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
264
|
+
return {
|
|
265
|
+
apply: false,
|
|
266
|
+
help: true,
|
|
267
|
+
preset: null,
|
|
268
|
+
targetDir: cwd,
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const remainingArgs = [...args]
|
|
273
|
+
|
|
274
|
+
if (remainingArgs[0] === 'init') {
|
|
275
|
+
remainingArgs.shift()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
let apply = false
|
|
279
|
+
let preset = null
|
|
280
|
+
let target = null
|
|
281
|
+
|
|
282
|
+
for (let index = 0; index < remainingArgs.length; index += 1) {
|
|
283
|
+
const arg = remainingArgs[index]
|
|
284
|
+
|
|
285
|
+
if (arg === '--') {
|
|
286
|
+
continue
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (arg === '--apply') {
|
|
290
|
+
apply = true
|
|
291
|
+
continue
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (arg === '--no-apply') {
|
|
295
|
+
apply = false
|
|
296
|
+
continue
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (arg === '--preset') {
|
|
300
|
+
const nextArg = remainingArgs[index + 1]
|
|
301
|
+
|
|
302
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
303
|
+
throw new Error('Missing value for --preset.')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
preset = nextArg
|
|
307
|
+
index += 1
|
|
308
|
+
continue
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (arg.startsWith('--preset=')) {
|
|
312
|
+
preset = arg.slice('--preset='.length)
|
|
313
|
+
|
|
314
|
+
if (!preset) {
|
|
315
|
+
throw new Error('Missing value for --preset.')
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
continue
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (arg.startsWith('-')) {
|
|
322
|
+
throw new Error(`Unknown option: ${arg}`)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (target !== null) {
|
|
326
|
+
throw new Error(`Unexpected extra argument: ${arg}`)
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
target = arg
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (preset && !apply) {
|
|
333
|
+
throw new Error('Use --preset together with --apply.')
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
apply,
|
|
338
|
+
help: false,
|
|
339
|
+
preset,
|
|
340
|
+
targetDir: path.resolve(cwd, target ?? '.'),
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function getHelpText(commandName = 'ace-pack') {
|
|
345
|
+
return `ACE Pack - install AI project memory into a repository.
|
|
346
|
+
|
|
347
|
+
Usage:
|
|
348
|
+
${commandName} init [target] [--apply] [--preset <name>]
|
|
349
|
+
${commandName} [target]
|
|
243
350
|
|
|
244
|
-
|
|
245
|
-
|
|
351
|
+
Recommended:
|
|
352
|
+
npx ace-pack@latest init
|
|
353
|
+
pnpm dlx ace-pack init
|
|
354
|
+
|
|
355
|
+
Options:
|
|
356
|
+
--apply Run ace:onboard -- --apply after installation.
|
|
357
|
+
--preset <name> Apply a built-in project preset during onboarding.
|
|
358
|
+
-h, --help Show this help.
|
|
359
|
+
|
|
360
|
+
Examples:
|
|
361
|
+
npx ace-pack@latest init
|
|
362
|
+
npx ace-pack@latest init D:\\All\\alex-work\\my-project --apply
|
|
363
|
+
pnpm dlx ace-pack init . --apply
|
|
364
|
+
|
|
365
|
+
Do not use npm install ace-pack for setup. ACE is a scaffold CLI: run init so it
|
|
366
|
+
can add AGENTS.md, .ai/*, scripts/*, and package.json commands to the project.
|
|
367
|
+
`
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function getPackageManagerFromPackageJson(packageJson) {
|
|
371
|
+
if (typeof packageJson.packageManager !== 'string') {
|
|
372
|
+
return null
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const packageManagerName = packageJson.packageManager.split('@')[0]
|
|
376
|
+
|
|
377
|
+
return KNOWN_PACKAGE_MANAGERS.has(packageManagerName) ? packageManagerName : null
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function getPackageManagerFromUserAgent(userAgent = process.env.npm_config_user_agent) {
|
|
381
|
+
if (!userAgent) {
|
|
382
|
+
return null
|
|
246
383
|
}
|
|
247
384
|
|
|
248
|
-
|
|
385
|
+
const packageManagerName = userAgent.split('/')[0]
|
|
386
|
+
|
|
387
|
+
return KNOWN_PACKAGE_MANAGERS.has(packageManagerName) ? packageManagerName : null
|
|
249
388
|
}
|
|
250
389
|
|
|
251
|
-
export function
|
|
390
|
+
export async function detectPackageManager(rootDir) {
|
|
391
|
+
const packageJsonContent = await readTextIfExists(path.join(rootDir, 'package.json'))
|
|
392
|
+
|
|
393
|
+
if (packageJsonContent !== null) {
|
|
394
|
+
const packageJson = JSON.parse(stripByteOrderMark(packageJsonContent))
|
|
395
|
+
const packageManager = getPackageManagerFromPackageJson(packageJson)
|
|
396
|
+
|
|
397
|
+
if (packageManager) {
|
|
398
|
+
return packageManager
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const lockFileChecks = [
|
|
403
|
+
['pnpm-lock.yaml', 'pnpm'],
|
|
404
|
+
['package-lock.json', 'npm'],
|
|
405
|
+
['npm-shrinkwrap.json', 'npm'],
|
|
406
|
+
['yarn.lock', 'yarn'],
|
|
407
|
+
['bun.lock', 'bun'],
|
|
408
|
+
['bun.lockb', 'bun'],
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
for (const [filename, packageManager] of lockFileChecks) {
|
|
412
|
+
if (await fileExists(path.join(rootDir, filename))) {
|
|
413
|
+
return packageManager
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return getPackageManagerFromUserAgent() ?? 'npm'
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
export function formatScriptCommand(packageManager, scriptName, args = []) {
|
|
421
|
+
const extraArgs = args.length > 0 ? args.join(' ') : ''
|
|
422
|
+
|
|
423
|
+
if (packageManager === 'npm') {
|
|
424
|
+
return `npm run ${scriptName}${extraArgs ? ` -- ${extraArgs}` : ''}`
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (packageManager === 'yarn') {
|
|
428
|
+
return `yarn ${scriptName}${extraArgs ? ` ${extraArgs}` : ''}`
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (packageManager === 'bun') {
|
|
432
|
+
return `bun run ${scriptName}${extraArgs ? ` ${extraArgs}` : ''}`
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
return `pnpm ${scriptName}${extraArgs ? ` -- ${extraArgs}` : ''}`
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function formatPowerShellPnpmCommand(command) {
|
|
439
|
+
return command.replace(/^pnpm /u, 'pnpm.cmd ')
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
export async function printInstallResult(result, options = {}) {
|
|
443
|
+
const packageManager = options.packageManager ?? (await detectPackageManager(result.targetDir))
|
|
444
|
+
|
|
252
445
|
if (result.updatedFiles.length === 0 && result.createdFiles.length === 0) {
|
|
253
446
|
process.stderr.write(`ACE pack is already up to date in ${result.targetDir}\n`)
|
|
254
447
|
}
|
|
@@ -261,14 +454,62 @@ export function printInstallResult(result) {
|
|
|
261
454
|
process.stderr.write(`Created: ${result.createdFiles.join(', ')}\n`)
|
|
262
455
|
}
|
|
263
456
|
|
|
264
|
-
|
|
457
|
+
if (result.onboarding?.applied) {
|
|
458
|
+
process.stderr.write(`Onboarded: ${result.onboarding.writtenFiles.join(', ')}\n`)
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const nextCommands = []
|
|
462
|
+
|
|
463
|
+
if (!result.onboarding?.applied) {
|
|
464
|
+
nextCommands.push(formatScriptCommand(packageManager, 'ace:onboard', ['--apply']))
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
nextCommands.push(formatScriptCommand(packageManager, 'ace:check'))
|
|
468
|
+
nextCommands.push(formatScriptCommand(packageManager, 'ace:hub'))
|
|
469
|
+
|
|
470
|
+
process.stderr.write('\nNext:\n')
|
|
471
|
+
|
|
472
|
+
for (const command of nextCommands) {
|
|
473
|
+
process.stderr.write(` ${command}\n`)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (packageManager === 'pnpm' && process.platform === 'win32') {
|
|
477
|
+
process.stderr.write(
|
|
478
|
+
`\nWindows PowerShell note: if pnpm is blocked, use ${formatPowerShellPnpmCommand(
|
|
479
|
+
nextCommands[0],
|
|
480
|
+
)}\n`,
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export async function runInstallCli(args, options = {}) {
|
|
486
|
+
const commandName = options.commandName ?? 'ace-pack'
|
|
487
|
+
const parsedArgs = parseInstallArgs(args)
|
|
488
|
+
|
|
489
|
+
if (parsedArgs.help) {
|
|
490
|
+
process.stdout.write(getHelpText(commandName))
|
|
491
|
+
return
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
const result = await installAcePack(parsedArgs.targetDir)
|
|
495
|
+
|
|
496
|
+
if (parsedArgs.apply) {
|
|
497
|
+
result.onboarding = await onboardRepository(parsedArgs.targetDir, {
|
|
498
|
+
apply: true,
|
|
499
|
+
preset: parsedArgs.preset ?? undefined,
|
|
500
|
+
})
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
await printInstallResult(result)
|
|
265
504
|
}
|
|
266
505
|
|
|
267
506
|
const isMainModule =
|
|
268
507
|
process.argv[1] !== undefined && path.resolve(process.argv[1]) === currentFilePath
|
|
269
508
|
|
|
270
509
|
if (isMainModule) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
510
|
+
await runInstallCli(process.argv.slice(2)).catch((error) => {
|
|
511
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
512
|
+
process.stderr.write(`${message}\n\nRun ace-pack --help for usage.\n`)
|
|
513
|
+
process.exit(1)
|
|
514
|
+
})
|
|
274
515
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { fileURLToPath } from 'node:url'
|
|
4
4
|
|
|
5
|
-
import { installAcePack,
|
|
5
|
+
import { installAcePack, runInstallCli } from './install-ace-pack.mjs'
|
|
6
6
|
|
|
7
7
|
export const installAgentMemoryPack = installAcePack
|
|
8
8
|
|
|
@@ -11,7 +11,11 @@ const isMainModule =
|
|
|
11
11
|
process.argv[1] !== undefined && path.resolve(process.argv[1]) === currentFilePath
|
|
12
12
|
|
|
13
13
|
if (isMainModule) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
await runInstallCli(process.argv.slice(2), { commandName: 'agent-memory-pack' }).catch(
|
|
15
|
+
(error) => {
|
|
16
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
17
|
+
process.stderr.write(`${message}\n\nRun agent-memory-pack --help for usage.\n`)
|
|
18
|
+
process.exit(1)
|
|
19
|
+
},
|
|
20
|
+
)
|
|
17
21
|
}
|