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 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
- pnpm dlx ace-pack init
107
+ npx ace-pack@latest init
107
108
  ```
108
109
 
109
- Or with npm:
110
+ Then profile the project:
110
111
 
111
112
  ```bash
112
- npx ace-pack init
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
- pnpm dlx ace-pack init ./my-project
128
+ npx ace-pack@latest init ./my-project
119
129
  ```
120
130
 
121
- Profile the project:
131
+ Install and apply onboarding in one command:
122
132
 
123
133
  ```bash
124
- pnpm ace:onboard
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:classify
146
- pnpm.cmd ace:validate
172
+ pnpm.cmd ace:check
147
173
  ```
148
174
 
149
175
  Known SaaS monorepo? Apply the built-in preset:
@@ -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
- const [commandOrTarget, maybeTarget] = args
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
- if (commandOrTarget === 'init') {
245
- return maybeTarget ? path.resolve(cwd, maybeTarget) : cwd
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
- return commandOrTarget ? path.resolve(cwd, commandOrTarget) : cwd
385
+ const packageManagerName = userAgent.split('/')[0]
386
+
387
+ return KNOWN_PACKAGE_MANAGERS.has(packageManagerName) ? packageManagerName : null
249
388
  }
250
389
 
251
- export function printInstallResult(result) {
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
- process.stderr.write('Next: run pnpm.cmd ace:onboard\n')
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
- const targetDir = resolveTargetDir(process.argv.slice(2))
272
- const result = await installAcePack(targetDir)
273
- printInstallResult(result)
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, printInstallResult, resolveTargetDir } from './install-ace-pack.mjs'
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
- const targetDir = resolveTargetDir(process.argv.slice(2))
15
- const result = await installAcePack(targetDir)
16
- printInstallResult(result)
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ace-pack",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Zero-dependency cognitive architecture framework for AI-driven development.",
5
5
  "type": "module",
6
6
  "license": "MIT",