@lorekit/cli 1.25.0 → 1.26.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/src/write.mjs CHANGED
@@ -16,6 +16,16 @@
16
16
  // --ttl-days <n> Days until the memory auto-expires (1–365)
17
17
  // --org <slug> Write to this org (remote only)
18
18
  //
19
+ // Provenance — where the lesson is being recorded FROM. Derived automatically
20
+ // from git + the CI environment (repo, branch, commit, and the pull request
21
+ // from LOREKIT_PR / GITHUB_REF); the dashboard turns it into links back to the
22
+ // PR, branch and commit. Each can be overridden, and the whole thing skipped:
23
+ // --origin-repo <owner/name> Override the derived repository
24
+ // --origin-branch <name> Override the derived branch
25
+ // --origin-commit <sha> Override the derived commit
26
+ // --origin-pr <n> The pull request this lesson came out of
27
+ // --no-origin Record no provenance at all
28
+ //
19
29
  // Store targeting (default: remote if configured, else local):
20
30
  // --remote Force write to the remote store
21
31
  // --local Force write to the local offline store
@@ -32,6 +42,7 @@ import { resolveDenies } from './control.mjs';
32
42
  import { resolveStores, remoteUnavailableReason } from './stores.mjs';
33
43
  import { log, err, heading, status, c } from './util.mjs';
34
44
  import { parseScopeKey } from './lessons-view.mjs';
45
+ import { deriveOrigin, mergeOrigin } from './origin.mjs';
35
46
 
36
47
  // Read all of stdin to a string. Resolves to '' when stdin IS a TTY (no pipe).
37
48
  function readStdin() {
@@ -100,6 +111,32 @@ export async function write(args) {
100
111
  const ttlDays = args['ttl-days'] ? Number(args['ttl-days']) : undefined;
101
112
  const orgSlug = typeof args.org === 'string' ? args.org : undefined;
102
113
 
114
+ // ── Provenance ────────────────────────────────────────────────────────────
115
+ // Derived from git + CI unless --no-origin; explicit --origin-* flags win.
116
+ // A field that is neither supplied nor derivable is omitted, never sent as
117
+ // null — the server keeps the last KNOWN origin per field, so an omission
118
+ // must not erase what an earlier write recorded.
119
+ // An explicitly supplied PR number is a caller assertion, so a malformed one
120
+ // is a usage error — silently ignoring it would record no provenance while
121
+ // the user believes they set it. Derived values, by contrast, degrade quietly.
122
+ let originPr = null;
123
+ if (args['origin-pr'] !== undefined) {
124
+ originPr = Number(args['origin-pr']);
125
+ if (!Number.isInteger(originPr) || originPr < 1) {
126
+ err(`${c.red('Error:')} --origin-pr must be a positive integer (got ${args['origin-pr']})`);
127
+ return 1;
128
+ }
129
+ }
130
+
131
+ const origin = args['no-origin']
132
+ ? {}
133
+ : mergeOrigin(deriveOrigin({ cwd: root, env }), {
134
+ origin_repo: typeof args['origin-repo'] === 'string' ? args['origin-repo'] : null,
135
+ origin_branch: typeof args['origin-branch'] === 'string' ? args['origin-branch'] : null,
136
+ origin_commit: typeof args['origin-commit'] === 'string' ? args['origin-commit'] : null,
137
+ origin_pr: originPr,
138
+ });
139
+
103
140
  // ── Resolve deny constraints ───────────────────────────────────────────────
104
141
  const { localDenied, remoteDenied } = resolveDenies(root, { env });
105
142
 
@@ -162,6 +199,7 @@ export async function write(args) {
162
199
  ...(trigger ? { trigger } : {}),
163
200
  ...(ttlDays ? { ttl_days: ttlDays } : {}),
164
201
  ...(orgSlug ? { org: orgSlug } : {}),
202
+ ...origin,
165
203
  };
166
204
 
167
205
  let result;
@@ -192,6 +230,7 @@ export async function write(args) {
192
230
  tags,
193
231
  source_agent: sourceAgent || null,
194
232
  trigger: trigger || null,
233
+ origin,
195
234
  }, null, 2));
196
235
  } else {
197
236
  const verb = inserted === true ? 'Created' : inserted === false ? 'Updated' : 'Written';