ctxr-cli 0.25.0 → 0.27.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.
Files changed (63) hide show
  1. package/README.md +6 -4
  2. package/dist/commands/land.d.ts.map +1 -1
  3. package/dist/commands/land.js +48 -3
  4. package/dist/commands/land.js.map +1 -1
  5. package/dist/commands/publish-check.d.ts +12 -0
  6. package/dist/commands/publish-check.d.ts.map +1 -1
  7. package/dist/commands/publish-check.js +39 -7
  8. package/dist/commands/publish-check.js.map +1 -1
  9. package/dist/commands/serve.d.ts.map +1 -1
  10. package/dist/commands/serve.js +58 -5
  11. package/dist/commands/serve.js.map +1 -1
  12. package/dist/config/schema.d.ts +8 -0
  13. package/dist/config/schema.d.ts.map +1 -1
  14. package/dist/config/schema.js +7 -0
  15. package/dist/config/schema.js.map +1 -1
  16. package/dist/core/approval/lander.d.ts +38 -0
  17. package/dist/core/approval/lander.d.ts.map +1 -0
  18. package/dist/core/approval/lander.js +80 -0
  19. package/dist/core/approval/lander.js.map +1 -0
  20. package/dist/core/approval/sessions.d.ts +1 -1
  21. package/dist/core/approval/sessions.d.ts.map +1 -1
  22. package/dist/core/approval/sessions.js +8 -8
  23. package/dist/core/approval/sessions.js.map +1 -1
  24. package/dist/core/browse/approve.d.ts +8 -3
  25. package/dist/core/browse/approve.d.ts.map +1 -1
  26. package/dist/core/browse/approve.js +77 -26
  27. package/dist/core/browse/approve.js.map +1 -1
  28. package/dist/core/browse/memo.d.ts +7 -0
  29. package/dist/core/browse/memo.d.ts.map +1 -0
  30. package/dist/core/browse/memo.js +56 -0
  31. package/dist/core/browse/memo.js.map +1 -0
  32. package/dist/core/browse/review.d.ts +2 -1
  33. package/dist/core/browse/review.d.ts.map +1 -1
  34. package/dist/core/browse/review.js +10 -4
  35. package/dist/core/browse/review.js.map +1 -1
  36. package/dist/core/browse/serving-state.d.ts +45 -0
  37. package/dist/core/browse/serving-state.d.ts.map +1 -0
  38. package/dist/core/browse/serving-state.js +108 -0
  39. package/dist/core/browse/serving-state.js.map +1 -0
  40. package/dist/core/checks/organize-checks.js +2 -2
  41. package/dist/core/checks/organize-checks.js.map +1 -1
  42. package/dist/core/errors.d.ts +4 -0
  43. package/dist/core/errors.d.ts.map +1 -1
  44. package/dist/core/errors.js +11 -0
  45. package/dist/core/errors.js.map +1 -1
  46. package/dist/core/git/exec.js +6 -7
  47. package/dist/core/git/exec.js.map +1 -1
  48. package/dist/core/git/land-lock.d.ts +25 -0
  49. package/dist/core/git/land-lock.d.ts.map +1 -0
  50. package/dist/core/git/land-lock.js +91 -0
  51. package/dist/core/git/land-lock.js.map +1 -0
  52. package/dist/core/publish/lifespan.d.ts +5 -5
  53. package/dist/core/publish/lifespan.d.ts.map +1 -1
  54. package/dist/core/publish/lifespan.js +13 -8
  55. package/dist/core/publish/lifespan.js.map +1 -1
  56. package/dist/core/publish/send-copies.d.ts +12 -0
  57. package/dist/core/publish/send-copies.d.ts.map +1 -0
  58. package/dist/core/publish/send-copies.js +15 -0
  59. package/dist/core/publish/send-copies.js.map +1 -0
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. package/package.json +1 -1
  63. package/templates/skills/ctxr-publish.md +37 -1
@@ -0,0 +1,91 @@
1
+ import { hostname } from 'node:os';
2
+ import { open, readFile, stat, unlink } from 'node:fs/promises';
3
+ import { unlinkSync } from 'node:fs';
4
+ import path from 'node:path';
5
+ /**
6
+ * write-lifecycle spec ("One landing runs per repository at a time";
7
+ * serve-lands-approved-changes D4): an exclusive pid file in the repository's
8
+ * common git directory, which every worktree and every container mounting the
9
+ * store shares — the same shape git uses for `index.lock`.
10
+ *
11
+ * Stale when its process is gone. Only the host that wrote it can tell, so a
12
+ * lock from another host (another container) is stale only once it is older
13
+ * than any landing run could be.
14
+ */
15
+ export const LAND_LOCK_FILE = 'ctxr-land.lock';
16
+ const FOREIGN_STALE_MS = 2 * 60 * 60 * 1000;
17
+ function alive(pid) {
18
+ try {
19
+ process.kill(pid, 0);
20
+ return true;
21
+ }
22
+ catch (err) {
23
+ // EPERM: it exists but belongs to someone else.
24
+ return err.code === 'EPERM';
25
+ }
26
+ }
27
+ async function readHolder(file) {
28
+ try {
29
+ const parsed = JSON.parse(await readFile(file, 'utf8'));
30
+ return typeof parsed.pid === 'number' && typeof parsed.host === 'string' ? { pid: parsed.pid, host: parsed.host } : null;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ async function stale(file, holder, now) {
37
+ if (holder !== null && holder.host === hostname())
38
+ return !alive(holder.pid);
39
+ try {
40
+ return now - (await stat(file)).mtimeMs > FOREIGN_STALE_MS;
41
+ }
42
+ catch {
43
+ return true;
44
+ }
45
+ }
46
+ export async function acquireLandLock(git, cwd, now = Date.now) {
47
+ const common = (await git.run(['rev-parse', '--git-common-dir'], { cwd })).stdout.trim();
48
+ const file = path.join(path.resolve(cwd, common), LAND_LOCK_FILE);
49
+ const me = { pid: process.pid, host: hostname() };
50
+ for (let attempt = 0; attempt < 2; attempt++) {
51
+ try {
52
+ const handle = await open(file, 'wx');
53
+ await handle.writeFile(JSON.stringify(me));
54
+ await handle.close();
55
+ }
56
+ catch (err) {
57
+ if (err.code !== 'EEXIST')
58
+ throw err;
59
+ const holder = await readHolder(file);
60
+ if (attempt === 0 && (await stale(file, holder, now()))) {
61
+ await unlink(file).catch(() => undefined);
62
+ continue;
63
+ }
64
+ return { acquired: false, holder: holder ?? { pid: 0, host: 'unknown' } };
65
+ }
66
+ // A landing stopped by its parent (serve shutting down) must not leave the lock behind.
67
+ const onSignal = (signal) => {
68
+ try {
69
+ unlinkSync(file);
70
+ }
71
+ catch {
72
+ // already gone
73
+ }
74
+ process.exit(signal === 'SIGINT' ? 130 : 143);
75
+ };
76
+ process.once('SIGTERM', onSignal);
77
+ process.once('SIGINT', onSignal);
78
+ return {
79
+ acquired: true,
80
+ async release() {
81
+ process.off('SIGTERM', onSignal);
82
+ process.off('SIGINT', onSignal);
83
+ const holder = await readHolder(file);
84
+ if (holder?.pid === me.pid && holder.host === me.host)
85
+ await unlink(file).catch(() => undefined);
86
+ },
87
+ };
88
+ }
89
+ return { acquired: false, holder: { pid: 0, host: 'unknown' } };
90
+ }
91
+ //# sourceMappingURL=land-lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"land-lock.js","sourceRoot":"","sources":["../../../src/core/git/land-lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAC/C,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAS5C,SAAS,KAAK,CAAC,GAAW;IACxB,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,gDAAgD;QAChD,OAAQ,GAA6B,CAAC,IAAI,KAAK,OAAO,CAAC;IACzD,CAAC;AACH,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAwB,CAAC;QAC/E,OAAO,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAC3H,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,KAAK,UAAU,KAAK,CAAC,IAAY,EAAE,MAAyB,EAAE,GAAW;IACvE,IAAI,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE;QAAE,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC7E,IAAI,CAAC;QACH,OAAO,GAAG,GAAG,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,gBAAgB,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAc,EAAE,GAAW,EAAE,GAAG,GAAiB,IAAI,CAAC,GAAG;IAC7F,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,kBAAkB,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;IACzF,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC;IAClE,MAAM,EAAE,GAAe,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC;IAE9D,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YACtC,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;YAC3C,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,MAAM,GAAG,CAAC;YAChE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;YACtC,IAAI,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC;gBACxD,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBAC1C,SAAS;YACX,CAAC;YACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;QAC5E,CAAC;QAED,wFAAwF;QACxF,MAAM,QAAQ,GAAG,CAAC,MAAsB,EAAQ,EAAE;YAChD,IAAI,CAAC;gBACH,UAAU,CAAC,IAAI,CAAC,CAAC;YACnB,CAAC;YAAC,MAAM,CAAC;gBACP,eAAe;YACjB,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAChD,CAAC,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACjC,OAAO;YACL,QAAQ,EAAE,IAAI;YACd,KAAK,CAAC,OAAO;gBACX,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;gBACjC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBAChC,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;gBACtC,IAAI,MAAM,EAAE,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI;oBAAE,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACnG,CAAC;SACF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;AAClE,CAAC"}
@@ -26,12 +26,12 @@ export declare const DISPOSABLE_REVIEW_DAYS = 30;
26
26
  */
27
27
  export declare function lifespanOf(relativePath: string, publishPath: string): PageLifespan | null;
28
28
  /**
29
- * The calendar date leading a disposable page's folder name, as `YYYY-MM-DD`,
30
- * or `null` when the name carries none or the date is not a real one
31
- * (`2026-13-45`, `2026-02-30`). Round-tripping through `Date.UTC` is what
32
- * rejects a day the month does not have.
29
+ * The calendar date leading a dated name, as `YYYY-MM-DD`, or `null` when the
30
+ * name carries none or the date is not a real one (`2026-13-45`,
31
+ * `2026-02-30`). Round-tripping through `Date.UTC` is what rejects a day the
32
+ * month does not have.
33
33
  */
34
- export declare function parseDisposableDate(folderName: string): string | null;
34
+ export declare function parseLeadingDate(name: string): string | null;
35
35
  /** Whole days from `date` to `today`, both `YYYY-MM-DD`. */
36
36
  export declare function daysBetween(date: string, today: string): number;
37
37
  //# sourceMappingURL=lifespan.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lifespan.d.ts","sourceRoot":"","sources":["../../../src/core/publish/lifespan.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,YAAY,CAAC;AAEtD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C,mHAAmH;AACnH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAMzC;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAMzF;AAKD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAOrE;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D"}
1
+ {"version":3,"file":"lifespan.d.ts","sourceRoot":"","sources":["../../../src/core/publish/lifespan.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,MAAM,YAAY,GAAG,WAAW,GAAG,YAAY,CAAC;AAEtD;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,eAAe,CAAC;AAE/C,mHAAmH;AACnH,eAAO,MAAM,sBAAsB,KAAK,CAAC;AAMzC;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAMzF;AAUD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO5D;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/D"}
@@ -34,16 +34,21 @@ export function lifespanOf(relativePath, publishPath) {
34
34
  const firstSegment = normalized.slice(prefix.length + 1).split('/')[0];
35
35
  return firstSegment === DISPOSABLE_SEGMENT ? 'disposable' : 'permanent';
36
36
  }
37
- /** The `YYYY-MM-DD-` lead a disposable page's folder name carries, stamped by `ctxr publish new --disposable`. */
38
- const DISPOSABLE_DATE_RE = /^(\d{4})-(\d{2})-(\d{2})-./;
39
37
  /**
40
- * The calendar date leading a disposable page's folder name, as `YYYY-MM-DD`,
41
- * or `null` when the name carries none or the date is not a real one
42
- * (`2026-13-45`, `2026-02-30`). Round-tripping through `Date.UTC` is what
43
- * rejects a day the month does not have.
38
+ * The `YYYY-MM-DD-` lead of a dated name: a disposable page's folder, stamped
39
+ * by `ctxr publish new --disposable`, and a page's send copy without its
40
+ * extension (file-pdfs-beside-their-page D4). At least one character
41
+ * must follow the date.
44
42
  */
45
- export function parseDisposableDate(folderName) {
46
- const match = DISPOSABLE_DATE_RE.exec(folderName);
43
+ const LEADING_DATE_RE = /^(\d{4})-(\d{2})-(\d{2})-./;
44
+ /**
45
+ * The calendar date leading a dated name, as `YYYY-MM-DD`, or `null` when the
46
+ * name carries none or the date is not a real one (`2026-13-45`,
47
+ * `2026-02-30`). Round-tripping through `Date.UTC` is what rejects a day the
48
+ * month does not have.
49
+ */
50
+ export function parseLeadingDate(name) {
51
+ const match = LEADING_DATE_RE.exec(name);
47
52
  if (!match)
48
53
  return null;
49
54
  const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
@@ -1 +1 @@
1
- {"version":3,"file":"lifespan.js","sourceRoot":"","sources":["../../../src/core/publish/lifespan.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE/C,mHAAmH;AACnH,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAEzC,SAAS,gBAAgB,CAAC,WAAmB;IAC3C,OAAO,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,YAAoB,EAAE,WAAmB;IAClE,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,YAAY,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,YAAY,KAAK,kBAAkB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;AAC1E,CAAC;AAED,kHAAkH;AAClH,MAAM,kBAAkB,GAAG,4BAA4B,CAAC;AAExD;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACtD,IAAI,IAAI,CAAC,cAAc,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACjH,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAa;IACrD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;AACvG,CAAC"}
1
+ {"version":3,"file":"lifespan.js","sourceRoot":"","sources":["../../../src/core/publish/lifespan.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,YAAY,CAAC;AAE/C,mHAAmH;AACnH,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAEzC,SAAS,gBAAgB,CAAC,WAAmB;IAC3C,OAAO,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,YAAoB,EAAE,WAAmB;IAClE,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,UAAU,GAAG,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;IACpD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,MAAM,YAAY,GAAG,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,YAAY,KAAK,kBAAkB,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC;AAC1E,CAAC;AAED;;;;;GAKG;AACH,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAErD;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAClF,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACtD,IAAI,IAAI,CAAC,cAAc,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE,KAAK,KAAK,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,KAAK,GAAG;QAAE,OAAO,IAAI,CAAC;IACjH,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAa;IACrD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,KAAK,YAAY,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,YAAY,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC;AACvG,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * send-copies-beside-their-page: a send copy is a file built from a note and
3
+ * sent to someone — a record of what went out on a day. Directly in a page
4
+ * folder, beside `index.html`, a file in one of these formats is one; anywhere
5
+ * below it, the same file is an input the page uses (file-pdfs-beside-their-page
6
+ * D3). A closed list, so a missed format goes unchecked rather than failing a
7
+ * page over a file nobody sent (D1). Adding a format is one entry here.
8
+ */
9
+ export declare const SEND_COPY_EXTENSIONS: readonly string[];
10
+ /** The send-copy extension `fileName` carries, lower-cased, or `null` when it is not a send copy. */
11
+ export declare function sendCopyExtension(fileName: string): string | null;
12
+ //# sourceMappingURL=send-copies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send-copies.d.ts","sourceRoot":"","sources":["../../../src/core/publish/send-copies.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAAwC,CAAC;AAE3F,qGAAqG;AACrG,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGjE"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * send-copies-beside-their-page: a send copy is a file built from a note and
3
+ * sent to someone — a record of what went out on a day. Directly in a page
4
+ * folder, beside `index.html`, a file in one of these formats is one; anywhere
5
+ * below it, the same file is an input the page uses (file-pdfs-beside-their-page
6
+ * D3). A closed list, so a missed format goes unchecked rather than failing a
7
+ * page over a file nobody sent (D1). Adding a format is one entry here.
8
+ */
9
+ export const SEND_COPY_EXTENSIONS = ['.pdf', '.docx', '.xlsx', '.pptx'];
10
+ /** The send-copy extension `fileName` carries, lower-cased, or `null` when it is not a send copy. */
11
+ export function sendCopyExtension(fileName) {
12
+ const lower = fileName.toLowerCase();
13
+ return SEND_COPY_EXTENSIONS.find((extension) => lower.endsWith(extension)) ?? null;
14
+ }
15
+ //# sourceMappingURL=send-copies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send-copies.js","sourceRoot":"","sources":["../../../src/core/publish/send-copies.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAE3F,qGAAqG;AACrG,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;IACrC,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,IAAI,IAAI,CAAC;AACrF,CAAC"}
package/dist/version.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Kept in sync with package.json's "version" field. */
2
- export declare const CLI_VERSION = "0.25.0";
2
+ export declare const CLI_VERSION = "0.27.0";
3
3
  /**
4
4
  * The npm package this executable is published as. Bound here, beside the
5
5
  * version, because the release check resolves the latest published release by
package/dist/version.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Kept in sync with package.json's "version" field. */
2
- export const CLI_VERSION = '0.25.0';
2
+ export const CLI_VERSION = '0.27.0';
3
3
  /**
4
4
  * The npm package this executable is published as. Bound here, beside the
5
5
  * version, because the release check resolves the latest published release by
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxr-cli",
3
- "version": "0.25.0",
3
+ "version": "0.27.0",
4
4
  "description": "Context store for AI coding agents — markdown and git, catalog and graph retrieval, no vector database.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -96,7 +96,8 @@ this choice. Why: a page filed under a word that names nothing helps no reader.
96
96
 
97
97
  Never start the page's own name with a date (`YYYY-` or `YYYY-MM-DD-`). That naming is reserved for
98
98
  the frozen pages that `ctxr publish new --disposable` makes. The rule applies to the final segment, not
99
- to the folders above it. The command refuses to overwrite an existing folder. If a folder already
99
+ to the folders above it. It does not apply to a send copy in the page's folder, which starts with
100
+ the date you made it (step 8). The command refuses to overwrite an existing folder. If a folder already
100
101
  exists for this subject, edit its files directly. Do not run `publish new` again. Why: a rename or an
101
102
  overwrite breaks a link that somebody already has.
102
103
 
@@ -133,6 +134,8 @@ Run `ctxr publish check <path>`. It runs the mechanical half of the checklist:
133
134
  with a real date at the start of its name;
134
135
  - for a permanent page, the note it represents (`represented-note`) and that note's recorded hash
135
136
  (`represented-note-hash`).
137
+ - for each send copy at the page folder's root, a file name that starts with a real date
138
+ (`send-copy-name`).
136
139
 
137
140
  The command reports the page's lifespan. It exits non-zero and names every failing check. Fix all of
138
141
  them before you report the page as ready. The command answers only what the files can show. Why: the
@@ -186,6 +189,39 @@ changed after the stamp. To confirm a stale page, do these steps in order:
186
189
  `ctxr publish stamp` never overwrites a marker. Why: a stamp without a review confirms nothing, so
187
190
  the stale report stays until somebody removes the marker.
188
191
 
192
+ **Send copies of the page.** A send copy is a file that you make from the note and send to somebody.
193
+ It is a `.pdf`, `.docx`, `.xlsx` or `.pptx` file. Make a send copy only when you send one. Why: every
194
+ send copy is kept, so a send copy for each edit fills the folder.
195
+
196
+ Write the send copy into the page's folder, next to `index.html`. Start its file name with the date
197
+ you make it: `<YYYY-MM-DD>-<name>.<extension>`. `ctxr publish check` fails a send copy at the folder
198
+ root that has no real date (`send-copy-name`). `ctxr publish check --json` lists each send copy with
199
+ its address. Give that address when you hand on the send copy.
200
+
201
+ A send copy is never a page on its own. A document that you send as a PDF, a Word file, a workbook or
202
+ a deck still needs an `index.html` for the same content. Why: every command that reads pages finds a
203
+ page by its `index.html`. The stamp on its README then reports the page and its send copies as stale
204
+ together.
205
+
206
+ When the page changes, write a new dated send copy. Do not change or remove an earlier one. Why: an
207
+ earlier file can be the one a recipient got, and the folder then shows what you sent on each date.
208
+
209
+ Put a file that the page uses, but that you did not make, in the page's `assets/` folder. Why:
210
+ `ctxr publish check` reads only the files at the folder root as the page's send copies.
211
+
212
+ The store chooses how to make a send copy. You can print `index.html` to a PDF, because it already
213
+ has a print rule. You can also use the store's own document or spreadsheet skill. Contexture does not
214
+ make send copies.
215
+
216
+ **A file that is its own source.** Some office files are not made from a note. An example is a
217
+ financial model that somebody edits by hand in a spreadsheet. Do not publish that file. Keep it in
218
+ the store, next to the note that describes it, with no date in its name. Why: the file is the source,
219
+ and the note describes it. A published copy would sit where retrieval does not look, and the stale
220
+ report would treat the note as the source. Git keeps the file's history. When you send a copy of the
221
+ file to somebody, that copy is a send copy: write it, dated, into the page's folder.
222
+
223
+ A file that you received from somebody is a capture. File it where the store keeps its captures.
224
+
189
225
  Never revise a disposable page after it lands. Why: it is a snapshot of one moment, and its link
190
226
  shows that moment. Make a new disposable page instead. Do not remove a disposable page yourself.
191
227
  `ctxr lint` lists disposable pages that are older than 30 days, and the operator decides which to