@timekast/factory 2.1.0 → 2.2.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.
@@ -4,8 +4,10 @@
4
4
  * A derivative documents a kit defect as a markdown file under `project/factory/`
5
5
  * (the `fx-factory-tickets` convention). Until now that file was a dead end: it sat
6
6
  * in the repo until somebody opened the Factory by hand. This command delivers it as
7
- * a GitHub issue in the Factory repo and stamps the resulting URL back into the
8
- * ticket's canonical `**GitHub issue:**` slot.
7
+ * a GitHub issue in the Factory repo and then DELETES the local file: it was only a
8
+ * draft, and from here on the issue is the one source of state (open / closed,
9
+ * labels). A local copy with its own status only drifted from GitHub, and a second
10
+ * `push` of it opened a duplicate issue (`fx-factory-tickets §6`).
9
11
  *
10
12
  * THREE THINGS THIS COMMAND DELIBERATELY DOES NOT DO:
11
13
  *
@@ -34,7 +36,7 @@
34
36
  * preflight degrades to a note + `exit 0` — the same `not-configured` contract
35
37
  * `factory backlog push` has without `BACKLOG_API_KEY`, never a stack trace.
36
38
  */
37
- import { existsSync, readFileSync, realpathSync, statSync, writeFileSync } from 'node:fs';
39
+ import { existsSync, readFileSync, realpathSync, statSync, unlinkSync } from 'node:fs';
38
40
  import path from 'node:path';
39
41
  import { execa } from 'execa';
40
42
  import prompts from 'prompts';
@@ -163,36 +165,6 @@ export function resolveTicketPath(rootDir, filePath) {
163
165
  return realTarget;
164
166
  }
165
167
  // ─────────────────────────────────────────────────────────────────────────────
166
- // Stamping (`fx-factory-tickets §6.2`)
167
- // ─────────────────────────────────────────────────────────────────────────────
168
- /** Canonical header field the delivered URL is stamped into. It is the ONLY field a tool writes. */
169
- const GITHUB_ISSUE_FIELD = '**GitHub issue:**';
170
- const ESTADO_FIELD = '**Estado:**';
171
- /**
172
- * Write `url` into the ticket's `**GitHub issue:**` slot, which is born holding an
173
- * em-dash and sits immediately after `**Estado:**`. Stamping the URL does NOT touch
174
- * `Estado` — delivering is not triaging (`fx-factory-tickets §6.2`).
175
- *
176
- * A ticket written before the convention has no such field; it is inserted right
177
- * after `Estado` rather than dropped, so the ticket stops being a dead end either
178
- * way. With no header block at all, nothing is changed and the caller warns.
179
- */
180
- export function stampGitHubIssueUrl(content, url) {
181
- const stamped = `${GITHUB_ISSUE_FIELD} ${url}`;
182
- const lines = content.split('\n');
183
- const fieldIdx = lines.findIndex((line) => line.startsWith(GITHUB_ISSUE_FIELD));
184
- if (fieldIdx >= 0) {
185
- lines[fieldIdx] = stamped;
186
- return { content: lines.join('\n'), changed: true };
187
- }
188
- const estadoIdx = lines.findIndex((line) => line.startsWith(ESTADO_FIELD));
189
- if (estadoIdx >= 0) {
190
- lines.splice(estadoIdx + 1, 0, stamped);
191
- return { content: lines.join('\n'), changed: true };
192
- }
193
- return { content, changed: false };
194
- }
195
- // ─────────────────────────────────────────────────────────────────────────────
196
168
  // Terminal safety
197
169
  // ─────────────────────────────────────────────────────────────────────────────
198
170
  /**
@@ -271,7 +243,7 @@ async function defaultConfirm(message) {
271
243
  *
272
244
  * Order matters and is asserted by the unit test: containment → read → size →
273
245
  * (`--dry-run` stops here) → gh session → publish gate → create → verify
274
- * assignee/label → stamp. Every guard that can reject runs BEFORE `gh` is ever
246
+ * assignee/label → delete the draft. Every guard that can reject runs BEFORE `gh` is ever
275
247
  * spawned.
276
248
  */
277
249
  export async function runTicketPush(filePath, flags = parseTicketPushFlags([]), rootDir = detectRepo(process.cwd()).repoRoot ?? process.cwd(), deps = {}) {
@@ -387,8 +359,8 @@ export async function runTicketPush(filePath, flags = parseTicketPushFlags([]),
387
359
  if (!url) {
388
360
  console.log(`✅ Issue creado en ${FACTORY_REPO}.`);
389
361
  console.log(stdout.trim());
390
- console.warn('⚠️ No se pudo leer la URL de la salida de `gh`, así que el ticket local no se estampó. ' +
391
- 'Copia la URL de arriba al campo `**GitHub issue:**`.');
362
+ console.warn('⚠️ No se pudo leer la URL de la salida de `gh`, así que el borrador local NO se borró. ' +
363
+ 'Revisa el issue de arriba y borra el archivo a mano (no lo vuelvas a entregar: abriría otro issue).');
392
364
  return;
393
365
  }
394
366
  console.log(`✅ Issue creado: ${url}`);
@@ -407,22 +379,19 @@ export async function runTicketPush(filePath, flags = parseTicketPushFlags([]),
407
379
  // permission is `read` — so the two notification mechanisms of this channel can
408
380
  // fail at once, without a word, unless we look.
409
381
  await verifyIssueFields(url, flags, labelDropped, assigneeDropped);
410
- // 6. Stamp the URL back. If this fails, the URL is already on screen (step above):
411
- // never leave the dev without the URL of an issue that already exists.
412
- const stamp = stampGitHubIssueUrl(body, url);
413
- if (!stamp.changed) {
414
- console.warn(`⚠️ \`${shown}\` no tiene el campo \`${GITHUB_ISSUE_FIELD}\` ni \`${ESTADO_FIELD}\`, así que no se estampó. ` +
415
- 'Agrega la URL a mano (`fx-factory-tickets §6.2`).');
416
- return;
417
- }
382
+ // 6. The draft is delivered: delete it. From here the issue is the only source of
383
+ // state. If this fails, the URL is already on screen (step above) — never leave
384
+ // the dev without the URL of an issue that already exists, and never fail a
385
+ // delivery that succeeded.
418
386
  try {
419
- (deps.writeFile ?? ((p, c) => writeFileSync(p, c, 'utf8')))(target, stamp.content);
420
- console.log(` URL estampada en \`${shown}\` (el \`Estado\` no cambia: entregar no es triar).`);
387
+ (deps.removeFile ?? unlinkSync)(target);
388
+ console.log(` Borré el borrador \`${shown}\`: el estado vive en el issue. ` +
389
+ 'Commitea el borrado (`docs(factory): …`).');
421
390
  }
422
391
  catch (error) {
423
- console.warn(`⚠️ El issue existe (${url}) pero \`${shown}\` no se pudo actualizar: ` +
392
+ console.warn(`⚠️ El issue existe (${url}) pero \`${shown}\` no se pudo borrar: ` +
424
393
  `${error instanceof Error ? error.message : String(error)}\n` +
425
- ` Copia la URL al campo \`${GITHUB_ISSUE_FIELD}\` a mano.`);
394
+ ' Bórralo a mano; entregarlo otra vez abriría un issue duplicado.');
426
395
  }
427
396
  }
428
397
  /**
package/dist/index.js CHANGED
@@ -191,8 +191,8 @@ Sobre \`backlog\`:
191
191
 
192
192
  Sobre \`ticket\`:
193
193
  \`ticket push <archivo>\` entrega un factory-ticket de \`project/factory/\` al repo
194
- del Factory como un issue de GitHub, y estampa la URL de vuelta en el campo
195
- \`**GitHub issue:**\` del ticket (el \`Estado\` no cambia: entregar no es triar).
194
+ del Factory como un issue de GitHub y, entregado, BORRA el archivo local: era un
195
+ borrador, y el estado vive en el issue (commitea el borrado con \`docs(factory): …\`).
196
196
  Cero credenciales nuevas: usa tu propia sesión de \`gh\`; sin sesión avisa y sale 0.
197
197
  El cuerpo se publica ÍNTEGRO en un repo compartido, así que hay compuerta:
198
198
  --dry-run Imprime destino, asignado, etiqueta y título; no crea nada
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timekast/factory",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Public, thin CLI to bootstrap and maintain TimeKast Factory derived projects.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",