@bluestep-systems/bspecs 0.11.1 → 0.13.0

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
@@ -46,11 +46,21 @@ devDependency; bare `npx b6p` resolves only inside a scaffolded project, where `
46
46
  From the parent directory where you want to create the project:
47
47
 
48
48
  ```sh
49
- bspecs
49
+ bspecs new
50
50
  ```
51
51
 
52
52
  The interactive wizard asks for the project name, client, and an optional description. When done, it generates the project directory with the full structure and (unless you opt out) runs `git init`.
53
53
 
54
+ ### Add the tooling to an existing project
55
+
56
+ Already have a project and just want the Claude Code tooling? From inside that project's directory:
57
+
58
+ ```sh
59
+ bspecs init
60
+ ```
61
+
62
+ `bspecs init` installs the full tooling tree **in place** and is strictly non-destructive: any file that already exists is left untouched. The one exception is `package.json` — the `@bluestep-systems/b6p-cli` devDependency (and the `b6p` script) are merged in, preserving everything else. It then writes the `bspecs.lock` so `bspecs sync` works going forward. At the end it prints a report of every file it skipped because the name already existed; to install the `bspecs` version of any of them, rename or move your local copy and run `bspecs init` again. The client-name prompt is optional — press Enter to default to `BlueStep Client`. (No `git init` — an existing project owns its own VCS.)
63
+
54
64
  ### Keep a project up to date
55
65
 
56
66
  When a new version of `bspecs` is published with improvements to skills, hooks, or instructions, update your global install and sync the project:
package/cli.js CHANGED
@@ -3,8 +3,8 @@ import { intro, outro, cancel, log } from '@clack/prompts';
3
3
  import { readFileSync } from 'fs';
4
4
  import { dirname, join } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
- import { runPrompts } from './src/prompts.js';
7
- import { scaffold } from './src/scaffold.js';
6
+ import { runPrompts, runInitPrompts } from './src/prompts.js';
7
+ import { scaffold, init } from './src/scaffold.js';
8
8
  import { sync } from './src/sync.js';
9
9
 
10
10
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -12,17 +12,22 @@ const pkg = JSON.parse(readFileSync(join(__dirname, 'package.json'), 'utf8'));
12
12
 
13
13
  const HELP = `bspecs — spec-driven BlueStep development with AI agents
14
14
 
15
- Scaffold a new BlueStep project with Claude Code skills, hooks, and
15
+ Scaffold and maintain BlueStep projects with Claude Code skills, hooks, and
16
16
  project conventions for spec-driven development.
17
17
 
18
18
  Usage:
19
- bspecs Run the interactive scaffolder in the current directory.
19
+ bspecs new Scaffold a brand-new project in a new subdirectory.
20
+ bspecs init Install the tooling into the current directory (non-destructive).
20
21
  bspecs sync Sync infrastructure files in the current project.
21
22
  bspecs -v Print version.
22
23
  bspecs -h Print this help.
23
24
 
24
25
  Options for bspecs sync:
25
26
  --silent Suppress all output (used by the SessionStart hook).
27
+
28
+ bspecs init never overwrites an existing file (package.json has the b6p-cli
29
+ devDependency merged in) and reports what it skipped so you can rename/move and
30
+ re-run. Its client-name prompt is optional — press Enter for "BlueStep Client".
26
31
  `;
27
32
 
28
33
  function parseArgs(argv) {
@@ -31,7 +36,9 @@ function parseArgs(argv) {
31
36
  if (flags.has('-v') || flags.has('--version')) return { mode: 'version' };
32
37
  if (flags.has('-h') || flags.has('--help')) return { mode: 'help' };
33
38
  if (positional[0] === 'sync') return { mode: 'sync', silent: flags.has('--silent') };
34
- return { mode: 'interactive' };
39
+ if (positional[0] === 'new') return { mode: 'new' };
40
+ if (positional[0] === 'init') return { mode: 'init' };
41
+ return { mode: 'help' };
35
42
  }
36
43
 
37
44
  async function main() {
@@ -52,6 +59,25 @@ async function main() {
52
59
 
53
60
  intro('bspecs — spec-driven BlueStep development with AI agents');
54
61
 
62
+ if (mode === 'init') {
63
+ const answers = await runInitPrompts();
64
+ if (!answers) {
65
+ cancel('Cancelled.');
66
+ process.exit(0);
67
+ }
68
+
69
+ await init(answers);
70
+
71
+ outro(
72
+ `bspecs tooling installed in ${process.cwd()}\n` +
73
+ ` Next steps:\n` +
74
+ ` npm install (if it did not run automatically)\n` +
75
+ ` npx -p @bluestep-systems/b6p-cli b6p auth set (platform credentials, once per machine)`
76
+ );
77
+ return;
78
+ }
79
+
80
+ // mode === 'new'
55
81
  const answers = await runPrompts();
56
82
  if (!answers) {
57
83
  cancel('Cancelled.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bluestep-systems/bspecs",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "description": "Spec-driven BlueStep development with AI agents — scaffolder and project conventions for Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
package/src/prompts.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { text, confirm, isCancel, cancel } from '@clack/prompts';
2
2
  import { existsSync } from 'fs';
3
- import { join } from 'path';
3
+ import { basename, join } from 'path';
4
4
 
5
5
  function titleCase(s) {
6
6
  return s
@@ -72,3 +72,39 @@ export async function runPrompts() {
72
72
  initGit,
73
73
  };
74
74
  }
75
+
76
+ // Prompts for `bspecs init` (install into the current directory). No folder-name
77
+ // question — the project name defaults to the cwd basename — and no git prompt.
78
+ // Client name is optional, falling back to 'BlueStep Client' on an empty Enter.
79
+ export async function runInitPrompts() {
80
+ const cwd = process.cwd();
81
+
82
+ const clientName = bail(
83
+ await text({
84
+ message: 'Client name (optional — press Enter for "BlueStep Client")',
85
+ placeholder: 'BlueStep Client',
86
+ })
87
+ );
88
+
89
+ const projectDescription = bail(
90
+ await text({
91
+ message: 'Project description (optional — gives Claude project context)',
92
+ placeholder: 'What does this project do? Press Enter to skip.',
93
+ })
94
+ );
95
+
96
+ const proceed = bail(
97
+ await confirm({
98
+ message: `Install bspecs tooling into ${cwd}?`,
99
+ initialValue: true,
100
+ })
101
+ );
102
+
103
+ if (!proceed) return null;
104
+
105
+ return {
106
+ projectName: basename(cwd),
107
+ clientName: (clientName && clientName.trim()) || 'BlueStep Client',
108
+ projectDescription: projectDescription || '',
109
+ };
110
+ }
package/src/scaffold.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { execSync } from 'child_process';
2
- import { join, dirname } from 'path';
2
+ import { join, dirname, basename, relative } from 'path';
3
3
  import { fileURLToPath } from 'url';
4
4
  import { readFileSync, writeFileSync, existsSync } from 'fs';
5
5
  import { log } from '@clack/prompts';
6
- import { ensureDir, copyTemplateTree, applyTemplate, TEMPLATES_DIR, sha256 } from './utils.js';
6
+ import { ensureDir, copyTemplateTree, applyTemplate, writeFile, mergePackageJson, TEMPLATES_DIR, sha256 } from './utils.js';
7
7
  import { SYNC_TARGETS } from './sync.js';
8
8
 
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -79,6 +79,11 @@ export async function scaffold(answers) {
79
79
  );
80
80
  }
81
81
 
82
+ printAuthReminder();
83
+ }
84
+
85
+ // One-time-per-machine BlueStep credential reminder, shared by `scaffold` and `init`.
86
+ function printAuthReminder() {
82
87
  log.info(
83
88
  [
84
89
  'Next step — set your BlueStep platform credentials (required, once per machine):',
@@ -93,6 +98,102 @@ export async function scaffold(answers) {
93
98
  );
94
99
  }
95
100
 
101
+ // `bspecs init`: install the template tree into the current directory without
102
+ // overwriting anything that already exists (package.json is the one exception —
103
+ // its devDependencies are merged). Writes the lock so `bspecs sync` works after.
104
+ export async function init(answers) {
105
+ const projectDir = process.cwd();
106
+
107
+ const vars = {
108
+ PROJECT_NAME: answers.projectName,
109
+ CLIENT_NAME: answers.clientName,
110
+ PROJECT_DESCRIPTION: answers.projectDescription,
111
+ SCAFFOLD_DATE: new Date().toISOString().split('T')[0],
112
+ };
113
+
114
+ const collect = { written: [], skipped: [] };
115
+
116
+ copyTemplateTree('root', projectDir, vars, {
117
+ skipExisting: true,
118
+ collect,
119
+ exclude: ['package.json.template'],
120
+ });
121
+ copyTemplateTree('claude', join(projectDir, '.claude'), vars, {
122
+ skipExisting: true,
123
+ collect,
124
+ makeExecutable: true,
125
+ });
126
+ copyTemplateTree('module', join(projectDir, '.claude', 'templates'), vars, {
127
+ skipExisting: true,
128
+ collect,
129
+ });
130
+
131
+ const pkgStatus = handlePackageJson(projectDir, vars, collect);
132
+
133
+ writeBspecsLock(projectDir, vars);
134
+
135
+ log.success('Tooling installed.');
136
+
137
+ checkPrettierOnPath();
138
+ installDependencies(basename(projectDir), projectDir);
139
+ printAuthReminder();
140
+
141
+ reportInstall(projectDir, collect, pkgStatus);
142
+
143
+ return { collect, pkgStatus };
144
+ }
145
+
146
+ // End-of-`init` summary. Lists every file left untouched because it already
147
+ // existed, with guidance to rename/move and re-run for the pristine version.
148
+ function reportInstall(projectDir, collect, pkgStatus) {
149
+ const parts = [`${collect.written.length} added`];
150
+ if (pkgStatus === 'merged') parts.push('1 merged (package.json)');
151
+ parts.push(`${collect.skipped.length} skipped`);
152
+ log.info(`Install summary: ${parts.join(', ')}.`);
153
+
154
+ if (collect.skipped.length > 0) {
155
+ const list = collect.skipped.map((p) => ' ' + relative(projectDir, p)).join('\n');
156
+ log.warn(
157
+ 'These files already existed and were left untouched:\n' +
158
+ list +
159
+ '\n\nTo install the bspecs version of any of them, rename or move your local copy and run `bspecs init` again.'
160
+ );
161
+ }
162
+ }
163
+
164
+ // package.json is the one file `init` may modify: if absent we write the template;
165
+ // if present we merge in the missing b6p-cli devDependency (mergePackageJson fails
166
+ // soft on malformed JSON). Returns 'written' | 'merged' | 'unchanged' | 'merge-failed'.
167
+ function handlePackageJson(projectDir, vars, collect) {
168
+ const dest = join(projectDir, 'package.json');
169
+ const rendered = applyTemplate(
170
+ readFileSync(join(TEMPLATES_DIR, 'root', 'package.json.template'), 'utf8'),
171
+ vars
172
+ );
173
+
174
+ if (!existsSync(dest)) {
175
+ writeFile(dest, rendered);
176
+ collect.written.push(dest);
177
+ return 'written';
178
+ }
179
+
180
+ const existing = readFileSync(dest, 'utf8');
181
+ const merged = mergePackageJson(existing, rendered);
182
+ if (merged === null) {
183
+ log.warn(
184
+ 'Existing package.json is not valid JSON — left untouched. Add the b6p CLI by hand:\n' +
185
+ ' "devDependencies": { "@bluestep-systems/b6p-cli": "^0.1.0" }'
186
+ );
187
+ collect.skipped.push(dest);
188
+ return 'merge-failed';
189
+ }
190
+ if (merged !== existing) {
191
+ writeFile(dest, merged);
192
+ return 'merged';
193
+ }
194
+ return 'unchanged';
195
+ }
196
+
96
197
  // Detect whether the freshly created project directory sits inside an existing
97
198
  // git repository (a parent has a .git). Running `git init` here would nest a
98
199
  // repo inside another, which surprises users and breaks the implementer agent's
package/src/utils.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { mkdirSync, writeFileSync, readFileSync, existsSync, chmodSync, readdirSync, statSync } from 'fs';
2
2
  import { createHash } from 'node:crypto';
3
- import { dirname, join } from 'path';
3
+ import { dirname, join, relative } from 'path';
4
4
  import { fileURLToPath } from 'url';
5
5
 
6
6
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -25,11 +25,22 @@ export function readTemplate(relativePath) {
25
25
  return readFileSync(join(TEMPLATES_DIR, relativePath), 'utf8');
26
26
  }
27
27
 
28
+ // Extra opts (all default off, so plain calls overwrite everything as before):
29
+ // skipExisting leaves existing dest files untouched (bspecs init); collect is a
30
+ // { written, skipped } accumulator of absolute paths; exclude lists source-relative
31
+ // paths (forward-slashed) to omit so the caller can handle them separately.
28
32
  export function copyTemplateTree(srcRel, destAbs, vars, opts = {}) {
29
- const { makeExecutable = false, stripTemplateExt = true } = opts;
33
+ const {
34
+ makeExecutable = false,
35
+ stripTemplateExt = true,
36
+ skipExisting = false,
37
+ collect = null,
38
+ exclude = [],
39
+ } = opts;
30
40
  const srcAbs = join(TEMPLATES_DIR, srcRel);
31
41
  if (!existsSync(srcAbs)) return;
32
- walk(srcAbs, srcAbs, destAbs, vars, { makeExecutable, stripTemplateExt });
42
+ const skip = new Set(exclude);
43
+ walk(srcAbs, srcAbs, destAbs, vars, { makeExecutable, stripTemplateExt, skipExisting, collect, skip });
33
44
  }
34
45
 
35
46
  function walk(rootSrc, src, dest, vars, opts) {
@@ -39,16 +50,26 @@ function walk(rootSrc, src, dest, vars, opts) {
39
50
  if (stats.isDirectory()) {
40
51
  walk(rootSrc, srcEntry, join(dest, entry), vars, opts);
41
52
  } else {
53
+ const srcRelPath = relative(rootSrc, srcEntry).split('\\').join('/');
54
+ if (opts.skip.has(srcRelPath)) continue;
55
+
42
56
  const targetName = opts.stripTemplateExt && entry.endsWith('.template')
43
57
  ? entry.slice(0, -'.template'.length)
44
58
  : entry;
45
59
  const destFile = join(dest, targetName);
60
+
61
+ if (opts.skipExisting && existsSync(destFile)) {
62
+ if (opts.collect) opts.collect.skipped.push(destFile);
63
+ continue;
64
+ }
65
+
46
66
  const raw = readFileSync(srcEntry, 'utf8');
47
67
  const rendered = applyTemplate(raw, vars);
48
68
  writeFile(destFile, rendered);
49
69
  if (opts.makeExecutable && destFile.endsWith('.sh')) {
50
70
  try { chmodSync(destFile, 0o755); } catch { /* Windows can't chmod, ignore */ }
51
71
  }
72
+ if (opts.collect) opts.collect.written.push(destFile);
52
73
  }
53
74
  }
54
75
  }
@@ -93,3 +114,32 @@ export function exists(path) {
93
114
  export function sha256(str) {
94
115
  return createHash('sha256').update(str, 'utf8').digest('hex');
95
116
  }
117
+
118
+ // Add only-missing template devDependencies and the `b6p` script into an existing
119
+ // package.json (bspecs init); existing values are never changed. Returns merged
120
+ // JSON text, or null if the existing content isn't valid JSON (caller fails soft).
121
+ export function mergePackageJson(existingContent, templateContent) {
122
+ let existing;
123
+ try {
124
+ existing = JSON.parse(existingContent);
125
+ } catch {
126
+ return null;
127
+ }
128
+ const template = JSON.parse(templateContent);
129
+
130
+ const tplDeps = template.devDependencies || {};
131
+ if (Object.keys(tplDeps).length > 0) {
132
+ existing.devDependencies = existing.devDependencies || {};
133
+ for (const [name, version] of Object.entries(tplDeps)) {
134
+ if (!(name in existing.devDependencies)) existing.devDependencies[name] = version;
135
+ }
136
+ }
137
+
138
+ const tplScripts = template.scripts || {};
139
+ if ('b6p' in tplScripts) {
140
+ existing.scripts = existing.scripts || {};
141
+ if (!('b6p' in existing.scripts)) existing.scripts.b6p = tplScripts.b6p;
142
+ }
143
+
144
+ return JSON.stringify(existing, null, 2) + '\n';
145
+ }
@@ -1,6 +1,5 @@
1
1
  ---
2
2
  description: BlueStep Platform overview — architecture (Relate/Connect/Manage/BsJs), the b6p CLI, sync, and component lifecycle. Read when orienting on the platform, working with the b6p CLI, or troubleshooting pull/push sync.
3
- applyTo: "**/.b6p_metadata.json"
4
3
  ---
5
4
 
6
5
  # B6P Platform Workflow
@@ -14,7 +13,7 @@ Platform orientation plus the workflow reference for sync, lifecycle, and the b6
14
13
  - [Data hierarchy](#data-hierarchy)
15
14
  - [Script types](#script-types)
16
15
  - [b6p CLI workflow](#b6p-cli-workflow)
17
- - [`.b6p_metadata.json`](#b6p_metadatajson)
16
+ - [Sync metadata](#sync-metadata)
18
17
  - [Files Claude must never edit](#files-claude-must-never-edit)
19
18
  - [Imports — verification flow](#imports--verification-flow)
20
19
  - [When the CLI fails](#when-the-cli-fails)
@@ -113,27 +112,27 @@ A successful first pull:
113
112
  - Creates the `U######/` folder (if not present) and the `<ComponentName>/` subfolder under it
114
113
  - Populates `draft/scripts/`, `draft/info/`, and (in older modules) `draft/objects/`
115
114
  - Populates `declarations/` with the platform-generated `.d.ts` files, including `declarations/index.d.ts` (field/query/form declarations)
116
- - Writes `.b6p_metadata.json` at the component root for future pulls/pushes
115
+ - Records the component's sync metadata (WebDAV id, file hashes, script key) so future pulls/pushes can resolve it
117
116
 
118
117
  Subsequent pulls verify per-file integrity and only rewrite files whose content changed.
119
118
 
120
119
  ### Push a component
121
120
 
122
- The cleanest way to push an already-pulled component is `--file`, which lets the CLI derive the destination DAV URL from local `.b6p_metadata.json`:
121
+ The cleanest way to push an already-pulled component is `--file`, which lets the CLI derive the destination DAV URL from the recorded sync metadata:
123
122
 
124
123
  ```
125
124
  npx b6p push --file "U######/<Component>/draft/scripts/app.ts"
126
125
  ```
127
126
 
128
- Any file inside the component works as the `--file` argument; the CLI walks up to find `.b6p_metadata.json`. Same per-file integrity check applies — only changed files are uploaded. The platform compiles after receiving the push.
127
+ Any file inside the component works as the `--file` argument; the CLI walks up to find the component root and looks up its recorded sync metadata. Same per-file integrity check applies — only changed files are uploaded. The platform compiles after receiving the push.
129
128
 
130
129
  ### Fallback: VS Code extension
131
130
 
132
131
  If the `b6p` CLI fails (network, lock, auth), the VS Code **b6p extension** is an equivalent fallback for pull/push operations.
133
132
 
134
- ## `.b6p_metadata.json`
133
+ ## Sync metadata
135
134
 
136
- Auto-managed by the CLI. Tracks WebDAV IDs, last-pull / last-push timestamps, file hashes, and script keys. **Never edit manually.**
135
+ Auto-managed by the CLI and stored internally (no longer a `.b6p_metadata.json` file in the workspace; legacy files are migrated on first run). Tracks WebDAV IDs, last-pull / last-push timestamps, file hashes, and script keys, so pull/push/audit can resolve each component. Not something you edit.
137
136
 
138
137
  ## Files Claude must never edit
139
138
 
@@ -173,7 +172,7 @@ Common causes:
173
172
  Fallbacks:
174
173
 
175
174
  1. The VS Code b6p extension provides the same pull/push operations through the editor UI.
176
- 2. As a last resort, the platform UI lets you download/upload component files manually — but losing `.b6p_metadata.json` consistency makes future CLI sync fragile.
175
+ 2. As a last resort, the platform UI lets you download/upload component files manually — but manual uploads bypass the CLI's recorded sync metadata, making future CLI sync fragile.
177
176
 
178
177
  ## When `B.commit()` is required
179
178
 
@@ -327,7 +327,7 @@ Each project root has a `tsconfig.json`. BlueStep projects run with **`strict: f
327
327
  }
328
328
  ```
329
329
 
330
- Do not run `tsc` locally — the platform compiles on push (a hook blocks local `tsc`). For why local builds / `rootDir` are avoided, see `conventions/tsc-rootdir.md`.
330
+ Do not run `tsc` locally — the platform compiles on push (a hook blocks local `tsc`).
331
331
 
332
332
  ### Graal compatibility (server-side)
333
333
 
@@ -47,19 +47,15 @@ Entries read: `path — what it covers. Load when <trigger>.` Match the trigger
47
47
 
48
48
  ## conventions/ — build, deploy, and workflow directives
49
49
 
50
- - [conventions/always-snapshot.md](conventions/always-snapshot.md) — pull/push/snapshot workflow: use the CLI scripts (never fetch manually) and snapshot after every change via push → pull → snapshot. Load when syncing a component or unsure of the snapshot routine.
51
50
  - [conventions/blueiq-no-ai-branding.md](conventions/blueiq-no-ai-branding.md) — never put "AI" in front-facing BlueIQ text; the brand is always "BlueIQ". Load when writing user-visible BlueIQ copy.
52
51
  - [conventions/date-format.md](conventions/date-format.md) — `addSearch` needs `MM/DD/YYYY` for date fields; `YYYY/MM/DD` passes validation but silently fails to filter, and raw Java `LocalDate` objects also fail. Load when filtering a query by a date field.
53
52
  - [conventions/endpoint-approach.md](conventions/endpoint-approach.md) — research-first: gather API docs, read type declarations, and study existing examples before writing an endpoint. Load when starting a new endpoint.
54
53
  - [conventions/formula-patterns.md](conventions/formula-patterns.md) — correct BSJS formula patterns: form access, field reads/writes, HTTP, error handling, `B.*` utilities, DocumentLinkField. Load when writing a Formula or Post-Save script.
55
54
  - [conventions/no-global-dollar.md](conventions/no-global-dollar.md) — never define a global `$` in MergeReport scripts; pages use jQuery and you will silently break Save and all page interactivity. Load when adding client JS to a MergeReport.
56
- - [conventions/push-inner-draft.md](conventions/push-inner-draft.md) — pass the inner `draft/` folder (not the component root) to `push.js`, or files land in a `draft/draft/*` dead zone. Load when pushing a component with `push.js`.
57
55
  - [conventions/separate-files.md](conventions/separate-files.md) — don't pile CSS/HTML/JS into `app.ts`; use the `styles.css`/`index.html`/`script.ts` files the pulled folder already has. Load when structuring MergeReport files.
58
56
  - [conventions/single-script.md](conventions/single-script.md) — the server-side build compiles only root `static/script.ts` to `.build/script.js`; subdirectory `.ts` files are NOT compiled. Load when splitting client scripts into multiple files.
59
- - [conventions/snapshot-integrity.md](conventions/snapshot-integrity.md) — snapshots register cleanly only when `push.js` sends locally-compiled `.build/*` alongside source, otherwise a manual re-snapshot is needed. Load when a push leaves the snapshot needing manual re-registration.
60
57
  - [conventions/top-level-const-tdz.md](conventions/top-level-const-tdz.md) — endpoint/MergeReport scripts run top-down; declare every `const`/`let` above the entry `try` block or they are in TDZ when the handler fires. Load when ordering declarations in an endpoint or MergeReport.
61
58
  - [conventions/ts-in-template-literal.md](conventions/ts-in-template-literal.md) — the inline `<script>` inside a `B.out` template literal must be plain JS; any TypeScript syntax ships verbatim to the browser and breaks parsing. Load when emitting client JS through a `B.out` string.
62
- - [conventions/tsc-rootdir.md](conventions/tsc-rootdir.md) — compile endpoints with `--rootDir .` so output mirrors `scripts/app.ts` → `.build/scripts/app.js`; `--rootDir ./scripts` emits to `.build/app.js`, which the runtime ignores. Load when configuring the local TypeScript build for an endpoint.
63
59
 
64
60
  ## gotchas/ — sharp edges
65
61
 
@@ -15,4 +15,4 @@ A server-side BSJS merge report CAN access and parse an uploaded CSV at runtime.
15
15
 
16
16
  **Gotchas:** strip UTF-8 BOM (`` corrupts first header key on Excel/QuickBooks exports); handle `\r\n`; sanitize currency (`$`, thousands commas, `(123)` negatives) before parseFloat; pass charset if Windows-1252; streaming `.forReader()` avoids buffering huge files and sidesteps the "code 0 but real content" issue seen with `B.io.fromInputStream`.
17
17
 
18
- **Relevance:** unlocks the behavioral scorecard placeholder metrics that aren't in relational data — MRR, Monthly Profitability, Avg Contribution Margin (QuickBooks exports), NPS (survey results). Pattern: upload CSV to a Document field on a settings record; in app.ts `field.forReader(r => B.csv(r).toListOfObjects())`; the metric's Verify table can show the parsed rows. Docs: ~/.bluestep/docs/classes/CSV.md, DocumentLinkField.md, Document.md, Folder.md, B.Bluestep.IO.md.
18
+ **Relevance:** unlocks the behavioral scorecard placeholder metrics that aren't in relational data — MRR, Monthly Profitability, Avg Contribution Margin (QuickBooks exports), NPS (survey results). Pattern: upload CSV to a Document field on a settings record; in app.ts `field.forReader(r => B.csv(r).toListOfObjects())`; the metric's Verify table can show the parsed rows.
@@ -11,5 +11,5 @@ The alias is set per-endpoint in BlueStep admin and is not derivable from the sc
11
11
 
12
12
  **How to apply:**
13
13
  - When reporting test URLs to the user, ask what the friendly alias is — do not guess from the script name or fall back to the `/files/.../draft/` URL.
14
- - The `.b6p_metadata.json` `url` field is the editor URL, not the runtime URL.
14
+ - The DAV `url` the CLI recorded for the component is the editor URL, not the runtime URL.
15
15
  - If the user reports "500 / Error" on an endpoint, ask them to confirm the alias before adding script-side error handling.
@@ -79,7 +79,7 @@ The legacy `objects/imports.ts` `.require()` registration did this in code; it i
79
79
 
80
80
  ## Workspace Sync
81
81
 
82
- Each project has a `.b6p_metadata.json` file tracking sync state between the local workspace and the BlueStep server: script metadata (name, org ID, WebDAV ID), per-file last-sync timestamps, and build state. The `draft/` directory holds the working version of the code; the `.build/` directories hold the compiled JavaScript output from the TypeScript source.
82
+ The b6p CLI tracks sync state between the local workspace and the BlueStep server script metadata (name, org ID, WebDAV ID), per-file last-sync timestamps, and build state — in its own internal store (no longer a `.b6p_metadata.json` file in the workspace). The `draft/` directory holds the working version of the code; the `.build/` directories hold the compiled JavaScript output from the TypeScript source.
83
83
 
84
84
  ## Common Mistakes
85
85
 
@@ -20,6 +20,6 @@ The recurring **"merge report + memo field JSON hack"** — how Brandon and I re
20
20
  - Injected controls must have **NO `name` attribute** — named controls get submitted with the form → "problem storing the data". See [named controls submit](named-controls-submit.md).
21
21
  - Mount modals on `document.body` to escape the form's submit scope.
22
22
  - Borrow native relate icons (`/static/.../relate-icons/pencil.svg`, `trash.svg`) and call `window.fixAllSVG()` after render to recolor them like native lists.
23
- - Build pipeline / push rules: [tsc rootdir](../conventions/tsc-rootdir.md), [push inner draft](../conventions/push-inner-draft.md), [separate files](../conventions/separate-files.md), [always snapshot](../conventions/always-snapshot.md).
23
+ - File structure: [separate files](../conventions/separate-files.md). For sync, use the `/b6p-push` and `/b6p-pull` skills (`npx b6p`).
24
24
 
25
25
  **Reference implementations:** kaizenacademy/1465899 "Treatment Plan Review Comments Display" (original inspo, has Domains + lock); kaizenacademy/1466219 "Protocol Update Table" (notes-only + permission gating). Related JSON-in-memo dashboards: [crm dashboard inspo](crm-dashboard-inspo.md), visit dashboard.
@@ -44,7 +44,7 @@ test -f ~/.b6p/secrets.enc && echo OK
44
44
 
45
45
  If `$ARGUMENTS` contains a component path (e.g. `U######/Combined Scheduler`), use it. If empty, ask the user which component to audit.
46
46
 
47
- Confirm `.b6p_metadata.json` exists at the component root — without it, audit cannot determine the destination URL.
47
+ Confirm the component was pulled with `b6p` (so its sync metadata is recorded) — without that, audit cannot determine the destination URL. If it was never pulled here, pull it first.
48
48
 
49
49
  ### 2. Run the audit
50
50
 
@@ -16,7 +16,7 @@ b6p pull [options] <formula-url>
16
16
 
17
17
  The user copies the DAV URL from the component's page in the BlueStep platform UI. You cannot discover or infer it. There is no fallback that takes a name.
18
18
 
19
- A first pull creates the `U######/<ComponentName>/` folder (creating the U-folder if it does not exist) and populates `declarations/`, `draft/`, and `.b6p_metadata.json`.
19
+ A first pull creates the `U######/<ComponentName>/` folder (creating the U-folder if it does not exist), populates `declarations/` and `draft/`, and records the component's sync metadata.
20
20
 
21
21
  ## How to invoke `b6p`
22
22
 
@@ -49,7 +49,7 @@ test -f ~/.b6p/secrets.enc && echo OK
49
49
  - If `$ARGUMENTS` looks like a URL (starts with `http://` or `https://`), use it.
50
50
  - If `$ARGUMENTS` is empty or looks like a display name (no scheme), STOP and ask the user:
51
51
  > I need the **DAV URL** of the component, not its name. Copy it from the component's page in the BlueStep platform UI and paste it here.
52
- - Do NOT guess the URL. Do NOT try to derive it from `.b6p_metadata.json` of other components.
52
+ - Do NOT guess the URL. Do NOT try to derive it from another component's recorded metadata.
53
53
 
54
54
  ### 2. Run the pull
55
55
 
@@ -66,7 +66,6 @@ Capture the output — it prints the local path where the component landed.
66
66
  Parse the CLI output, or scan for the most recently modified `U######/<Name>/` directory under the project root. Confirm:
67
67
 
68
68
  - `declarations/` is populated
69
- - `.b6p_metadata.json` exists at the component root
70
69
  - `draft/info/metadata.json` and `draft/info/config.json` exist
71
70
  - `draft/scripts/app.ts` (or whatever `config.json:main` points at) exists
72
71
 
@@ -8,13 +8,13 @@ allowed-tools: Bash(npx b6p *) Bash(git*) Bash(test -f *)
8
8
 
9
9
  ## How `b6p push` actually works
10
10
 
11
- `b6p push` is most reliably driven by `--file <path>`, which tells the CLI to derive the destination DAV URL from the local file's metadata (via `.b6p_metadata.json`):
11
+ `b6p push` is most reliably driven by `--file <path>`, which tells the CLI to derive the destination DAV URL from the sync metadata it recorded for that component when it was pulled:
12
12
 
13
13
  ```
14
14
  b6p push --file <path-inside-component>
15
15
  ```
16
16
 
17
- Any file inside the component works as the `--file` argument; the CLI walks up to find `.b6p_metadata.json`.
17
+ Any file inside the component works as the `--file` argument; the CLI walks up to find the component root and looks up its recorded sync metadata.
18
18
 
19
19
  ## How to invoke `b6p`
20
20
 
@@ -44,7 +44,7 @@ If `$ARGUMENTS` contains a component path (relative to the project root), use it
44
44
 
45
45
  - Run `git status` to surface what changed and flag anything unexpected.
46
46
  - Briefly summarise the diff scope: "X files changed in `U######/<Component>/draft/`".
47
- - Confirm `.b6p_metadata.json` exists inside the component without it, `--file` cannot derive the destination URL.
47
+ - Confirm the component was pulled with `b6p` (so its sync metadata is recorded) `--file` resolves the destination URL from that metadata. If the component was never pulled here, pull it first.
48
48
 
49
49
  ### 3. Confirm with the user
50
50
 
@@ -1,25 +0,0 @@
1
- ---
2
- description: "BlueStep pull/push/snapshot workflow — always use the CLI scripts (never fetch manually) and always snapshot after every change via push → pull → snapshot"
3
- ---
4
-
5
- Always use the BlueStep CLI scripts. Never use WebFetch, curl, or the Write tool to interact with BlueStep files manually.
6
-
7
- - Pull: `node ~/.bluestep/pull.js <url>`
8
- - Push: `node ~/.bluestep/push.js [folder]`
9
- - Snapshot: `node ~/.bluestep/push.js --snapshot --comment "message" [folder]`
10
-
11
- **Why:** A full system handles auth, WebDAV, folder naming by display name, `.b6p_url.json` tracking, and GraphQL snapshot-history recording. Bypassing it breaks the whole workflow.
12
-
13
- **How to apply:** Any time the user says "pull", "push", or "snapshot" in a BlueStep context, use the CLI scripts. Credentials are in `~/.bluestep/config.json` — never prompt for them. Always snapshot after every code change — do not wait to be asked. Always include a `--comment` with a 1–3 sentence summary of what changed and why.
14
-
15
- ## Snapshot workflow — always push → pull → snapshot
16
-
17
- Never run `--snapshot` directly after editing. The correct sequence is:
18
-
19
- 1. `node ~/.bluestep/push.js [folder]` — push source files to draft (BlueStep compiles TypeScript server-side).
20
- 2. `node ~/.bluestep/pull.js <url>` — pull to get the freshly compiled `.build/` files from the server.
21
- 3. `node ~/.bluestep/push.js --snapshot --comment "summary of changes" [folder]` — now the snapshot has the correct compiled JS and records history.
22
-
23
- **Why:** `push.js` skips `.build/` directories (compiled artifacts), and BlueStep compiles TypeScript on the server. Snapshotting without pulling first captures stale local `.build/` files and reverts the compiled output to an older version. The pull step fetches the server-compiled JS before snapshotting. The `--comment` flag records a GraphQL history entry (author, timestamp, message) matching what the VS Code extension does.
24
-
25
- Related: [snapshot integrity](snapshot-integrity.md) (including locally-compiled `.build/*` in the snapshot `saveState`), [push inner draft](push-inner-draft.md), [tsc rootdir](tsc-rootdir.md).
@@ -1,21 +0,0 @@
1
- ---
2
- description: "Always pass the inner draft/ folder (not the component root) to push.js, otherwise files land in a draft/draft/* dead zone on BlueStep"
3
- ---
4
-
5
- When pushing a BlueStep component, always pass the **inner `draft/` folder** as the local-folder argument to `push.js`, never the component root.
6
-
7
- ```bash
8
- # CORRECT — relative paths come out as "scripts/app.ts", "static/script.ts"
9
- node ~/.bluestep/push.js [--snapshot --comment "..."] \
10
- "C:\...\Bluestep Pull Requests\<org>\<...path>\<Component Name>\draft" \
11
- "https://<org>.bluestep.net/files/<id>/draft/"
12
-
13
- # WRONG — relative paths come out as "draft/scripts/app.ts" and land in draft/draft/*
14
- node ~/.bluestep/push.js "...\<Component Name>" "..."
15
- ```
16
-
17
- **Why:** `push.js` (`~/.bluestep/push.js`, lines 388–396) walks the supplied local folder and computes each file's path relative to it, then appends that relative path to the target URL. If the local folder is the component root (which contains `draft/` as a subfolder), every file's relative path starts with `draft/`, and concatenated with a target URL that already ends in `/draft/` you get `.../draft/draft/scripts/app.ts` — a parallel ghost tree BlueStep does not load from. The live files BlueStep serves live at the URL root (`.../draft/scripts/...`), not under a nested `draft/`.
18
-
19
- **How to apply:** Every time you invoke `push.js` for a BlueStep component, the `localFolder` argument's last path segment must be `draft` (or `snapshot`). If you accidentally push at the wrong level, the symptom is silent staleness — pushes "succeed" but the live JS never updates, and browser caches and source maps mislead you into thinking the bug is in code that is actually fine. Diagnose by inspecting the BlueStep file tree for a duplicate `draft/` subfolder or unexpected files at the root.
20
-
21
- **Related — the deriver fails when components are nested deeper than `<org>/<scriptName>`.** `push.js`'s URL deriver expects exactly three path segments before `draft/` (org / scriptName / type). The Report System uses `summitridge/Report System/<Component>/draft` — four segments — so auto-derive fails. Fix: read each component's `.b6p_metadata.json` and pass the `url` explicitly as the second argument. All three Report System components (Maestro, Viewer, Builder) need this on every push.
@@ -1,23 +0,0 @@
1
- ---
2
- description: "BlueStep snapshots register cleanly only when push.js sends locally-compiled .build/* alongside source — otherwise the user has to re-snapshot manually"
3
- ---
4
-
5
- For BlueStep snapshots to register cleanly in the web UI without a manual re-snapshot, include the locally-compiled `.build/*.js` (and `.build/scripts/*.js`) files alongside the source `.ts`/`.html`/`.css` in the `saveState` that `push.js`'s `recordHistory` mutation records.
6
-
7
- **Why:** `push.js`'s `buildSaveState(localFolder, files)` reads files from `walkDir`, which respects `SKIP_DIRS`. If `.build` is in `SKIP_DIRS`, the `saveState` is source-only. BlueStep's snapshot UI compares the recorded `saveState` against the live filesystem (which has `.build/*.js` from server-side compilation), sees a mismatch, and flags the snapshot as incomplete — surfaced to the user as the familiar "go re-snapshot manually" pattern. When `.build/` IS included, source ↔ compiled match, the snapshot finalizes cleanly, and there is no need to touch the BlueStep UI to finish the operation.
8
-
9
- **How to apply:**
10
-
11
- - `~/.bluestep/push.js`'s `SKIP_DIRS` MUST be `new Set(['declarations', 'info'])` — NOT `new Set(['.build', 'declarations', 'info'])`. (`declarations/` and `info/` are genuinely server-generated and should stay excluded; `.build/` is locally compiled and should be included.) Changed 2026-05-01.
12
- - ALWAYS run `tsc` locally in each folder containing a `tsconfig.json` BEFORE invoking `push.js --snapshot`. Multi-tsconfig projects (e.g. root + `static/`) need a separate `tsc` per directory. Skipping the compile uploads a stale `.build/` that doesn't reflect current source, and the runtime silently diverges from what the snapshot's `.ts` says.
13
- - A second, independent reason to compile locally: BlueStep's server-side TS recompile after a `.ts` push has been observed to be unreliable. Pushing the locally-compiled `.build/*.js` directly bypasses any server-side compile lag or failure (the `export {};` `SyntaxError` saga from 2026-05-01 was caused by exactly this).
14
- - Verified empirically across many edits to `alpineacademy/1513841` and `1513859` (chatbot project) on 2026-05-01: with `.build/` included, every snapshot finalized cleanly in the BlueStep UI.
15
-
16
- **The pattern in shorthand:**
17
-
18
- 1. Edit source.
19
- 2. `cd <folder-with-tsconfig> && tsc` (repeat for each tsconfig in the project).
20
- 3. `node ~/.bluestep/push.js --snapshot --comment "..." <folder>`.
21
- 4. Done. No BlueStep UI interaction needed.
22
-
23
- Related: [always snapshot](always-snapshot.md), [tsc rootdir](tsc-rootdir.md), [push inner draft](push-inner-draft.md).
@@ -1,17 +0,0 @@
1
- ---
2
- description: "Compile BlueStep endpoints with --rootDir . so output mirrors scripts/app.ts → .build/scripts/app.js; --rootDir ./scripts emits to .build/app.js which the runtime ignores"
3
- ---
4
-
5
- When compiling a BlueStep endpoint locally, use:
6
-
7
- ```
8
- tsc --rootDir . --ignoreDeprecations 6.0 --skipLibCheck
9
- ```
10
-
11
- NOT `tsc --rootDir ./scripts ...`.
12
-
13
- **Why:** The BlueStep runtime loads `.build/scripts/app.js` (mirroring `scripts/app.ts`). With `--rootDir ./scripts`, tsc emits to `.build/app.js` (one level too high). The push succeeds and the snapshot succeeds, but the runtime keeps serving the old `.build/scripts/app.js`. Symptom: code edits appear to have no effect after pushing and snapshotting — silent staleness, no error, just wrong output.
14
-
15
- **How to apply:** Always run tsc from the `draft/` folder with `--rootDir .` (or omit `rootDir` entirely — tsc infers it from `tsconfig`). After compile, verify `.build/scripts/app.js` was updated, not `.build/app.js`. If a stale `.build/app.js` orphan exists from a past mistake, it can be left in place — the runtime ignores it.
16
-
17
- Confirmed on alpineacademy `/b/greatHair`, May 2026.