siesa-agents 2.1.80 → 2.1.81

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.
@@ -73,7 +73,7 @@ Parse the JSON object printed to stdout. The `status` field selects the next bra
73
73
 
74
74
  Proceed straight to Step 2 with `proposed_slug` and `recommended_dir`. Do **not** ask the user to confirm — the user has already accepted the auto-detection policy by invoking this skill.
75
75
 
76
- **`status: "single-repo"`** The repo name has no `docs`/`backend`/`frontend` segment, so the git remote already produces an unambiguous `project_id`. Stop and tell the user no `.siesa-project` is needed (mention the `fallback_project_id` value so they know what GCP will see). Do not write anything. If they later say "no, write one anyway with slug X", run Step 2 with that explicit slug and the directory they specify.
76
+ Single-repo projects also return `status: "ready"` (with `convention_match: false`). The proposed slug is the repo name lowercased and sanitized to kebab-case (e.g. `Siesa-Agents` `siesa-agents`) and `recommended_dir` is the git root itself. Proceed to Step 2 the same way no separate branch.
77
77
 
78
78
  **`status: "already-configured"`** — A `.siesa-project` exists above the current git root. Stop and report `existing_siesa_project.path` and `existing_siesa_project.value` to the user. Do not overwrite implicitly. If the user replies that they want it changed, run `--write` with `--force`, the new slug, and the same directory.
79
79
 
@@ -95,7 +95,9 @@ Parse the JSON response:
95
95
  - **`status: "exists"`** — A file is already there with a different value (the detect step missed it because the user pointed at a non-default dir, or a race). Show `current_value` vs `requested_slug` and ask the user whether to overwrite. If yes, re-run with `--force`.
96
96
  - **`status: "error"`** — Surface the error message verbatim and stop.
97
97
 
98
- For the `single-repo`, `already-configured` and `no-remote` branches where the user explicitly opts into writing, gather slug + directory inline and run the same `--write` command.
98
+ For the `already-configured` and `no-remote` branches where the user explicitly opts into writing, gather slug + directory inline and run the same `--write` command.
99
+
100
+ The file body written by `--write` is always `project_id=<slug>\n` (key=value form). `sa-emit.js` accepts both this format and a legacy bare-slug line for backward compatibility, so existing `.siesa-project` files keep working.
99
101
 
100
102
  ### Step 3 — Verify (optional)
101
103
 
@@ -129,7 +131,7 @@ business-<module>-<role>-<rest>
129
131
 
130
132
  The logical slug collapses `<role>` away: `business-pos-backend-pdv` → `pos-pdv`.
131
133
 
132
- For repo names that do not contain `docs`/`backend`/`frontend`, the script returns `single-repo` and no `.siesa-project` is needed.
134
+ For repo names that do not contain `docs`/`backend`/`frontend`, the script still returns `status: "ready"` but with `convention_match: false`. The slug is derived from the repo name (lowercased, sanitized to kebab-case) and the file is written at the git root of that single repo.
133
135
 
134
136
  ## Examples
135
137
 
@@ -157,11 +159,11 @@ Model: <runs sa-init-env.js — status "created">
157
159
 
158
160
  ```
159
161
  User: Set up .siesa-project for Siesa-Agents.
160
- Model: <runs --detect from Siesa-Agents/>
161
- Repo name "Siesa-Agents" has no docs/backend/frontend segment, so the git remote already produces an unambiguous project_id ("SiesaTeams/Siesa-Agents"). No .siesa-project is needed.
162
- Want to set a custom slug anyway?
163
- User: no, thanks
164
- Model: Nothing written. Existing fallback (SiesaTeams/Siesa-Agents) will continue to be reported.
162
+ Model: <runs sa-init-env.js silent>
163
+ <runs --detect from Siesa-Agents/>
164
+ <reads proposed_slug="siesa-agents", recommended_dir=C:\…\Siesa-Agents, convention_match=false>
165
+ <runs --write --slug siesa-agents --dir C:\…\Siesa-Agents>
166
+ Wrote C:\…\Siesa-Agents\.siesa-project (project_id=siesa-agents). sa-emit.js will now report project_id="siesa-agents" instead of "SiesaTeams/Siesa-Agents".
165
167
  ```
166
168
 
167
169
  **Example 3 — Already configured**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siesa-agents",
3
- "version": "2.1.80",
3
+ "version": "2.1.81",
4
4
  "description": "Paquete para instalar y configurar agentes SIESA en tu proyecto",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -134,17 +134,38 @@ const stateDir = path.join(os.homedir(), '.claude', 'observability')
134
134
  const epicIdMatch = story.match(/^(\d+)-/)
135
135
  const epicId = epicIdMatch ? epicIdMatch[1] : 'unknown'
136
136
 
137
+ function parseSiesaProject(content) {
138
+ // Preferred format: a single `project_id=<slug>` line. For backward compatibility a bare
139
+ // slug on its own line is also accepted. `#` introduces a comment. Returns the resolved
140
+ // slug, or null if nothing usable was found.
141
+ if (!content) return null
142
+ let firstBare = null
143
+ for (const raw of content.split(/\r?\n/)) {
144
+ const line = raw.trim()
145
+ if (!line || line.startsWith('#')) continue
146
+ const eq = line.indexOf('=')
147
+ if (eq !== -1) {
148
+ const key = line.slice(0, eq).trim()
149
+ const val = line.slice(eq + 1).trim()
150
+ if (key === 'project_id' && val) return val
151
+ continue
152
+ }
153
+ if (firstBare === null) firstBare = line
154
+ }
155
+ return firstBare
156
+ }
157
+
137
158
  function findSiesaProject(startDir) {
138
- // Walk up from startDir looking for a `.siesa-project` file. Returns its first non-empty
139
- // line trimmed, or null. Lets a parent dir override the git-remote-derived project_id —
159
+ // Walk up from startDir looking for a `.siesa-project` file. Returns the project_id slug
160
+ // it declares, or null. Lets a parent dir override the git-remote-derived project_id —
140
161
  // useful when several sub-repos (docs/backend/frontend) belong to the same logical project.
141
162
  let cur = path.resolve(startDir)
142
163
  for (let i = 0; i < 40; i++) {
143
164
  const candidate = path.join(cur, '.siesa-project')
144
165
  if (fs.existsSync(candidate)) {
145
166
  try {
146
- const first = fs.readFileSync(candidate, 'utf8').split(/\r?\n/)[0].trim()
147
- if (first) return first
167
+ const slug = parseSiesaProject(fs.readFileSync(candidate, 'utf8'))
168
+ if (slug) return slug
148
169
  } catch (_) {}
149
170
  }
150
171
  const parent = path.dirname(cur)
@@ -63,6 +63,27 @@ function tryExec(cmd, opts) {
63
63
  }
64
64
  }
65
65
 
66
+ // Parse a `.siesa-project` file body. Preferred format is `project_id=<slug>` (one key=value
67
+ // line, optional `#` comments). For backward compatibility a bare slug on its own line is
68
+ // also accepted. Returns the resolved slug, or null if nothing usable was found.
69
+ function parseSiesaProject(content) {
70
+ if (!content) return null
71
+ let firstBare = null
72
+ for (const raw of content.split(/\r?\n/)) {
73
+ const line = raw.trim()
74
+ if (!line || line.startsWith('#')) continue
75
+ const eq = line.indexOf('=')
76
+ if (eq !== -1) {
77
+ const key = line.slice(0, eq).trim()
78
+ const val = line.slice(eq + 1).trim()
79
+ if (key === 'project_id' && val) return val
80
+ continue
81
+ }
82
+ if (firstBare === null) firstBare = line
83
+ }
84
+ return firstBare
85
+ }
86
+
66
87
  // Walk up from `startDir` looking for an existing `.siesa-project`. Returns
67
88
  // { path, value } of the first match found, or null.
68
89
  function findExistingSiesaProject(startDir) {
@@ -71,8 +92,8 @@ function findExistingSiesaProject(startDir) {
71
92
  const candidate = path.join(cur, '.siesa-project')
72
93
  if (fs.existsSync(candidate)) {
73
94
  try {
74
- const first = fs.readFileSync(candidate, 'utf8').split(/\r?\n/)[0].trim()
75
- return { path: candidate, value: first }
95
+ const value = parseSiesaProject(fs.readFileSync(candidate, 'utf8'))
96
+ return { path: candidate, value }
76
97
  } catch (_) {
77
98
  return { path: candidate, value: null }
78
99
  }
@@ -186,15 +207,38 @@ function runDetect() {
186
207
  const derived = deriveLogicalSlug(repoName)
187
208
 
188
209
  if (!derived.matched) {
210
+ // Single-repo project: still write a `.siesa-project` so the project_id is explicit
211
+ // and self-documented. The slug is derived from the repo name, sanitized to kebab-case,
212
+ // and the file lands at the git root of the current project.
213
+ const singleRepoSlug = (repoName || '')
214
+ .toLowerCase()
215
+ .replace(/[^a-z0-9]+/g, '-')
216
+ .replace(/^-+|-+$/g, '')
217
+ if (!singleRepoSlug || !/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(singleRepoSlug)) {
218
+ emit({
219
+ status: 'no-remote',
220
+ message: 'Could not derive a valid kebab-case slug from the repo name. Pass --slug explicitly to write a .siesa-project.',
221
+ git_root: gitRoot,
222
+ remote_url: remoteUrl,
223
+ remote_repo: repoName,
224
+ })
225
+ process.exit(2)
226
+ }
189
227
  emit({
190
- status: 'single-repo',
191
- message: 'This repo name does not match the multi-repo convention (no docs|backend|frontend segment). sa-emit.js will report project_id based on the git remote, which is already unambiguous for single-repo projects. No .siesa-project is required.',
228
+ status: 'ready',
229
+ message: 'Single-repo project detected. The .siesa-project file will be written at the git root with a slug derived from the repo name.',
192
230
  git_root: gitRoot,
193
231
  remote_url: remoteUrl,
194
232
  remote_repo: repoName,
233
+ convention_match: false,
234
+ proposed_slug: singleRepoSlug,
235
+ convention_breakdown: null,
236
+ parent_candidates: [{ path: gitRoot, label: 'git root', levelsUp: 0 }],
237
+ recommended_dir: gitRoot,
195
238
  fallback_project_id: remoteParsed ? `${remoteParsed.org}/${remoteParsed.repo}` : null,
239
+ next_step_example: `node sa-init-project.js --write --slug "${singleRepoSlug}" --dir "${gitRoot}"`,
196
240
  })
197
- process.exit(2)
241
+ process.exit(0)
198
242
  }
199
243
 
200
244
  const candidates = listParentCandidates(gitRoot)
@@ -256,7 +300,7 @@ function runWrite(args) {
256
300
  const target = path.join(resolvedDir, '.siesa-project')
257
301
  if (fs.existsSync(target) && !force) {
258
302
  let current = null
259
- try { current = fs.readFileSync(target, 'utf8').split(/\r?\n/)[0].trim() } catch (_) {}
303
+ try { current = parseSiesaProject(fs.readFileSync(target, 'utf8')) } catch (_) {}
260
304
  emit({
261
305
  status: 'exists',
262
306
  message: '.siesa-project already exists at this location. Pass --force to overwrite.',
@@ -268,7 +312,7 @@ function runWrite(args) {
268
312
  }
269
313
 
270
314
  try {
271
- fs.writeFileSync(target, normalizedSlug + '\n', { encoding: 'utf8' })
315
+ fs.writeFileSync(target, `project_id=${normalizedSlug}\n`, { encoding: 'utf8' })
272
316
  } catch (err) {
273
317
  emit({ status: 'error', message: `Failed to write ${target}: ${err.message}` })
274
318
  process.exit(3)