ace-pack 0.1.0 → 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
@@ -40,8 +40,8 @@ ACE turns those soft expectations into local project structure:
40
40
  - `AGENTS.md` keeps stack, architecture, and workflow rules close to the code.
41
41
  - `.ai/memory-config.json` marks high-risk paths and keywords for the current
42
42
  repository.
43
- - `ace:validate` stays project-owned so every repo can define its real
44
- mechanical quality gate.
43
+ - `ace:validate` starts as an ACE memory check and can be replaced by each
44
+ repo with its real mechanical quality gate.
45
45
 
46
46
  ## ACE vs. Just Chatting With AI
47
47
 
@@ -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
108
+ ```
109
+
110
+ Then profile the project:
111
+
112
+ ```bash
113
+ npm run ace:onboard -- --apply
114
+ npm run ace:check
107
115
  ```
108
116
 
109
- Or with npm:
117
+ Prefer pnpm? Use the same flow through `pnpm dlx`:
110
118
 
111
119
  ```bash
112
- npx ace-pack init
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,8 +163,14 @@ 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`.
166
+ On Windows PowerShell, use `pnpm.cmd` if script execution policy blocks the
167
+ regular `pnpm` shim:
168
+
169
+ ```bash
170
+ pnpm.cmd dlx ace-pack init
171
+ pnpm.cmd ace:onboard -- --apply
172
+ pnpm.cmd ace:check
173
+ ```
139
174
 
140
175
  Known SaaS monorepo? Apply the built-in preset:
141
176
 
@@ -199,7 +234,7 @@ pnpm ace:classify
199
234
  | `ace:onboard -- --preset next-trpc-drizzle-saas --apply` | Applies the built-in Next.js + tRPC + Drizzle SaaS profile. |
200
235
  | `ace:onboard -- --check` | Fails if the repository is still unprofiled. |
201
236
  | `ace:classify` | Git diff risk analysis for small, standard, and large tasks. |
202
- | `ace:validate` | Project-owned mechanical quality gate. ACE never overwrites this script. |
237
+ | `ace:validate` | Default mechanical quality gate alias for `ace:check`. Projects may replace it with a stricter local gate. |
203
238
  | `ace:finish` | Adaptive closeout, memory documentation, reports, and reflection. |
204
239
  | `ace:hub` | Interactive context generator for copying focused project context into AI tools. |
205
240
 
@@ -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 = {
@@ -29,6 +30,10 @@ const REQUIRED_PACKAGE_SCRIPTS = {
29
30
  'ai:update:changed': 'node ./scripts/ai-update.mjs changed',
30
31
  }
31
32
 
33
+ const DEFAULT_PACKAGE_SCRIPTS = {
34
+ 'ace:validate': 'node ./scripts/check-agent-memory.mjs',
35
+ }
36
+
32
37
  const MANAGED_SCRIPT_FILES = [
33
38
  'ace-hub.mjs',
34
39
  'ace-onboard.mjs',
@@ -61,6 +66,7 @@ const currentFilePath = fileURLToPath(import.meta.url)
61
66
  const currentScriptDir = path.join(path.dirname(currentFilePath), 'scripts')
62
67
  const RUNNER_PACKAGE_DESCRIPTION =
63
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'])
64
70
 
65
71
  function normalizeTrailingNewline(content) {
66
72
  return content.endsWith('\n') ? content : `${content}\n`
@@ -78,6 +84,19 @@ async function readTextIfExists(filePath) {
78
84
  }
79
85
  }
80
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
+
81
100
  async function ensureDefaultAgentsFile(rootDir) {
82
101
  const agentsPath = path.join(rootDir, 'AGENTS.md')
83
102
  const existingContent = await readTextIfExists(agentsPath)
@@ -135,6 +154,15 @@ async function ensurePackageScripts(rootDir) {
135
154
  changed = true
136
155
  }
137
156
 
157
+ for (const [scriptName, scriptValue] of Object.entries(DEFAULT_PACKAGE_SCRIPTS)) {
158
+ if (scriptName in nextScripts) {
159
+ continue
160
+ }
161
+
162
+ nextScripts[scriptName] = scriptValue
163
+ changed = true
164
+ }
165
+
138
166
  if (!changed) {
139
167
  return false
140
168
  }
@@ -218,24 +246,202 @@ export async function installAcePack(targetDir) {
218
246
  createdFiles.push(...memoryResult.createdFiles)
219
247
  updatedFiles.push(...memoryResult.updatedFiles)
220
248
 
249
+ const createdFileSet = new Set(createdFiles)
250
+
221
251
  return {
222
252
  createdFiles,
223
253
  targetDir: normalizedTargetDir,
224
- updatedFiles,
254
+ updatedFiles: updatedFiles.filter((filePath) => !createdFileSet.has(filePath)),
225
255
  }
226
256
  }
227
257
 
228
258
  export function resolveTargetDir(args, cwd = process.cwd()) {
229
- 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]
230
284
 
231
- if (commandOrTarget === 'init') {
232
- return maybeTarget ? path.resolve(cwd, maybeTarget) : cwd
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.')
233
334
  }
234
335
 
235
- return commandOrTarget ? path.resolve(cwd, commandOrTarget) : cwd
336
+ return {
337
+ apply,
338
+ help: false,
339
+ preset,
340
+ targetDir: path.resolve(cwd, target ?? '.'),
341
+ }
236
342
  }
237
343
 
238
- export function printInstallResult(result) {
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]
350
+
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
383
+ }
384
+
385
+ const packageManagerName = userAgent.split('/')[0]
386
+
387
+ return KNOWN_PACKAGE_MANAGERS.has(packageManagerName) ? packageManagerName : null
388
+ }
389
+
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
+
239
445
  if (result.updatedFiles.length === 0 && result.createdFiles.length === 0) {
240
446
  process.stderr.write(`ACE pack is already up to date in ${result.targetDir}\n`)
241
447
  }
@@ -248,14 +454,62 @@ export function printInstallResult(result) {
248
454
  process.stderr.write(`Created: ${result.createdFiles.join(', ')}\n`)
249
455
  }
250
456
 
251
- 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)
252
504
  }
253
505
 
254
506
  const isMainModule =
255
507
  process.argv[1] !== undefined && path.resolve(process.argv[1]) === currentFilePath
256
508
 
257
509
  if (isMainModule) {
258
- const targetDir = resolveTargetDir(process.argv.slice(2))
259
- const result = await installAcePack(targetDir)
260
- 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
+ })
261
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.0",
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",
@@ -38,6 +38,7 @@
38
38
  "install:pack": "node ./install-ace-pack.mjs",
39
39
  "install:legacy": "node ./install-agent-memory-pack.mjs",
40
40
  "link:global": "pnpm link --global",
41
+ "ace:validate": "node ./scripts/check-agent-memory.mjs",
41
42
  "test": "vitest run"
42
43
  },
43
44
  "devDependencies": {
@@ -177,6 +177,9 @@ task.
177
177
  - Use \`pnpm ace:classify\` to select the adaptive task tier.
178
178
  - Run \`pnpm ace:onboard\` after fresh installation in an unfamiliar project
179
179
  before trusting project-specific risk rules.
180
+ - On Windows PowerShell, use \`pnpm.cmd ace:classify\`,
181
+ \`pnpm.cmd ace:validate\`, and similar commands if script execution policy
182
+ blocks the \`pnpm\` shim.
180
183
  - For large or high-risk standard tasks, complete \`.ai/current-task.md\`
181
184
  Business Value and Technical Approach before writing code.
182
185
  - Treat \`.ai/*\` as the current source of task context and handoff state.
@@ -218,6 +221,10 @@ Before starting work:
218
221
  7. Read \`.ai/work-log.md\` only when you need extra historical context.
219
222
  8. If the memory files are missing, run \`pnpm ace:init\`.
220
223
 
224
+ Command note: examples use \`pnpm\`. On Windows PowerShell, use
225
+ \`pnpm.cmd ace:classify\`, \`pnpm.cmd ace:validate\`, and similar commands if
226
+ the \`pnpm\` shim is blocked by execution policy.
227
+
221
228
  Legacy commands such as \`pnpm ai:task:classify\`, \`pnpm ai:task:finish\`,
222
229
  and \`pnpm agent-memory:init\` remain supported for compatibility.
223
230