dorfl 0.14.1 → 0.14.3
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/dist/advance-ci-template.d.ts.map +1 -1
- package/dist/advance-ci-template.js +10 -4
- package/dist/advance-ci-template.js.map +1 -1
- package/dist/advance-lifecycle-template.d.ts.map +1 -1
- package/dist/advance-lifecycle-template.js +78 -25
- package/dist/advance-lifecycle-template.js.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/close-job-template.d.ts +1 -1
- package/dist/close-job-template.d.ts.map +1 -1
- package/dist/close-job-template.js +17 -3
- package/dist/close-job-template.js.map +1 -1
- package/dist/install-ci-action-pins.d.ts +98 -0
- package/dist/install-ci-action-pins.d.ts.map +1 -0
- package/dist/install-ci-action-pins.js +165 -0
- package/dist/install-ci-action-pins.js.map +1 -0
- package/dist/install-ci-core.d.ts +84 -0
- package/dist/install-ci-core.d.ts.map +1 -1
- package/dist/install-ci-core.js +120 -14
- package/dist/install-ci-core.js.map +1 -1
- package/dist/install-ci-github.d.ts +15 -1
- package/dist/install-ci-github.d.ts.map +1 -1
- package/dist/install-ci-github.js +32 -0
- package/dist/install-ci-github.js.map +1 -1
- package/dist/install-ci.d.ts.map +1 -1
- package/dist/install-ci.js +21 -0
- package/dist/install-ci.js.map +1 -1
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +24 -7
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +10 -5
- package/dist/intake.js.map +1 -1
- package/dist/mint-adr.d.ts.map +1 -1
- package/dist/mint-adr.js +4 -1
- package/dist/mint-adr.js.map +1 -1
- package/dist/slug-namespace.d.ts +4 -3
- package/dist/slug-namespace.d.ts.map +1 -1
- package/dist/slug-namespace.js +28 -3
- package/dist/slug-namespace.js.map +1 -1
- package/dist/slug-safety.d.ts +55 -0
- package/dist/slug-safety.d.ts.map +1 -0
- package/dist/slug-safety.js +76 -0
- package/dist/slug-safety.js.map +1 -0
- package/dist/triage-persist.d.ts.map +1 -1
- package/dist/triage-persist.js +12 -3
- package/dist/triage-persist.js.map +1 -1
- package/dist/verify-workflow-template.d.ts +1 -1
- package/dist/verify-workflow-template.d.ts.map +1 -1
- package/dist/verify-workflow-template.js +16 -3
- package/dist/verify-workflow-template.js.map +1 -1
- package/package.json +3 -2
- package/src/advance-ci-template.ts +12 -4
- package/src/advance-lifecycle-template.ts +80 -25
- package/src/cli.ts +2 -2
- package/src/close-job-template.ts +17 -3
- package/src/install-ci-action-pins.ts +184 -0
- package/src/install-ci-core.ts +166 -14
- package/src/install-ci-github.ts +40 -1
- package/src/install-ci.ts +25 -0
- package/src/intake-trigger-template.ts +28 -7
- package/src/intake.ts +10 -5
- package/src/mint-adr.ts +4 -1
- package/src/slug-namespace.ts +33 -5
- package/src/slug-safety.ts +83 -0
- package/src/triage-persist.ts +13 -7
- package/src/verify-workflow-template.ts +16 -3
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SAFE-SLUG character set, and the one sanitiser every slug-producing path
|
|
3
|
+
* shares.
|
|
4
|
+
*
|
|
5
|
+
* A slug is not just a label: it becomes a file name (`work/<folder>/<slug>.md`),
|
|
6
|
+
* part of a git ref (`work/<slug>`, `work/intake-task-<slug>`), a command-line
|
|
7
|
+
* argument (`dorfl advance "task:<slug>"`, one per CI matrix leg) and text in
|
|
8
|
+
* commit messages. Several producers draft slugs from content an outsider can
|
|
9
|
+
* influence: `intake` from an issue's title and thread, the triage and apply
|
|
10
|
+
* rungs from an agent verdict. Before this module the only transform was
|
|
11
|
+
* `paramCase`, which keeps `$(`, backticks, quotes, `;`, `|` and `/`, and the
|
|
12
|
+
* triage/ADR paths only `trim()`med, so a drafted `../x` escaped its folder.
|
|
13
|
+
*
|
|
14
|
+
* Two functions, two jobs:
|
|
15
|
+
*
|
|
16
|
+
* - {@link toSafeSlug} is for PRODUCERS: it turns any drafted text into a slug
|
|
17
|
+
* made only of ASCII letters, digits and single hyphens. Its output always
|
|
18
|
+
* passes {@link isSafeSlug}.
|
|
19
|
+
* - {@link isSafeSlug} is for CONSUMERS: the slug resolvers refuse any slug
|
|
20
|
+
* outside the safe set, which covers slugs no producer here minted (a
|
|
21
|
+
* frontmatter `slug:` a human or agent wrote by hand). It is deliberately a
|
|
22
|
+
* little wider than what {@link toSafeSlug} emits (it also allows `.` and
|
|
23
|
+
* `_` and mixed case) so every slug already in use in a real `work/` tree
|
|
24
|
+
* keeps resolving: those characters are inert in a file name, a ref and a
|
|
25
|
+
* quoted shell word. `..` is still refused (path traversal), as is a leading
|
|
26
|
+
* `-` (it would read as a command-line option) or `.` (a hidden file), and so
|
|
27
|
+
* is a `.lock` suffix (git refuses it in a ref component).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The longest slug either function accepts. Real slugs in this repo top out
|
|
32
|
+
* around 105 characters; 120 leaves room without letting a drafted essay become
|
|
33
|
+
* a file name or ref component.
|
|
34
|
+
*/
|
|
35
|
+
export const MAX_SLUG_LENGTH = 120;
|
|
36
|
+
|
|
37
|
+
/** Letters, digits, `.`, `_`, `-`; starts and ends with a letter or digit. */
|
|
38
|
+
const SAFE_SLUG_RE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
39
|
+
|
|
40
|
+
/** Git refuses a ref component ending in `.lock` (its lock-file suffix). */
|
|
41
|
+
const GIT_LOCK_SUFFIX_RE = /\.lock$/i;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether `slug` is safe to use as a file name, a git ref component and a
|
|
45
|
+
* command-line argument (see the module doc for the exact set).
|
|
46
|
+
*/
|
|
47
|
+
export function isSafeSlug(slug: string): boolean {
|
|
48
|
+
return (
|
|
49
|
+
slug.length > 0 &&
|
|
50
|
+
slug.length <= MAX_SLUG_LENGTH &&
|
|
51
|
+
SAFE_SLUG_RE.test(slug) &&
|
|
52
|
+
!slug.includes('..') &&
|
|
53
|
+
!GIT_LOCK_SUFFIX_RE.test(slug)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Turn drafted text into a safe slug: accents are folded to their base letter,
|
|
59
|
+
* every run of anything other than an ASCII letter or digit becomes ONE hyphen,
|
|
60
|
+
* leading/trailing hyphens are dropped, and the result is capped at
|
|
61
|
+
* {@link MAX_SLUG_LENGTH}. Case is preserved (callers that want lower case, like
|
|
62
|
+
* `intake`, `paramCase` first). Returns `''` when nothing usable is left, so the
|
|
63
|
+
* caller's existing "no slug to derive" refusal fires instead of a junk name.
|
|
64
|
+
*/
|
|
65
|
+
export function toSafeSlug(input: string): string {
|
|
66
|
+
return input
|
|
67
|
+
.normalize('NFKD')
|
|
68
|
+
.replace(/[\u0300-\u036f]/g, '')
|
|
69
|
+
.replace(/[^A-Za-z0-9]+/g, '-')
|
|
70
|
+
.replace(/^-+|-+$/g, '')
|
|
71
|
+
.slice(0, MAX_SLUG_LENGTH)
|
|
72
|
+
.replace(/-+$/, '');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Keep a slug that is already safe EXACTLY as it is, and sanitise one that is
|
|
77
|
+
* not. The producers use this so every input that worked before still yields
|
|
78
|
+
* the same slug, and only an unsafe one changes.
|
|
79
|
+
*/
|
|
80
|
+
export function ensureSafeSlug(input: string): string {
|
|
81
|
+
const trimmed = input.trim();
|
|
82
|
+
return isSafeSlug(trimmed) ? trimmed : toSafeSlug(trimmed);
|
|
83
|
+
}
|
package/src/triage-persist.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {resolveSidecarIdentity, sidecarPathFor} from './sidecar.js';
|
|
|
11
11
|
import type {TriageAutoKind} from './triage-gate.js';
|
|
12
12
|
import {renderTaskBody, renderSpecBody} from './buildable-body.js';
|
|
13
13
|
import {setFrontmatterMarker} from './frontmatter.js';
|
|
14
|
+
import {ensureSafeSlug} from './slug-safety.js';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* The engine-owned TRIAGE PERSIST (spec `advance-loop`, task `advance-rung-triage`,
|
|
@@ -433,7 +434,9 @@ export async function promoteObservation(
|
|
|
433
434
|
const note = options.note ?? (() => {});
|
|
434
435
|
|
|
435
436
|
const {slug: obsSlug} = resolveSidecarIdentity(item);
|
|
436
|
-
|
|
437
|
+
// The drafted slug comes from an agent verdict: an unsafe one (a `/`, `..`,
|
|
438
|
+
// shell metacharacters) is sanitised, a safe one is kept exactly as drafted.
|
|
439
|
+
const newSlug = ensureSafeSlug(options.newSlug ?? obsSlug);
|
|
437
440
|
const artifact = options.artifact ?? 'task';
|
|
438
441
|
if (newSlug === '') {
|
|
439
442
|
return {
|
|
@@ -460,13 +463,16 @@ export async function promoteObservation(
|
|
|
460
463
|
// review-nits back-pointer); this is the triage-promote lineage. Matching on this
|
|
461
464
|
// explicit field — not the slug — is what keeps an unrelated same-slug task from
|
|
462
465
|
// false-positiving into a benign skip.
|
|
466
|
+
// A drafted `stubContent` carries its own frontmatter, and the ledger reads
|
|
467
|
+
// `slug:` before the file name, so stamp the SAFE slug over whatever the
|
|
468
|
+
// draft said: otherwise a drafted `slug: ../x` would become the item's
|
|
469
|
+
// identity even though the file name was sanitised.
|
|
470
|
+
const drafted =
|
|
471
|
+
options.stubContent !== undefined
|
|
472
|
+
? setFrontmatterMarker(options.stubContent, 'slug', newSlug)
|
|
473
|
+
: buildPromotedBody(artifact, newSlug, readItem(cwd, itemPath, env));
|
|
463
474
|
const content = setFrontmatterMarker(
|
|
464
|
-
ensureTaskDispatchable(
|
|
465
|
-
artifact,
|
|
466
|
-
newSlug,
|
|
467
|
-
options.stubContent ??
|
|
468
|
-
buildPromotedBody(artifact, newSlug, readItem(cwd, itemPath, env)),
|
|
469
|
-
),
|
|
475
|
+
ensureTaskDispatchable(artifact, newSlug, drafted),
|
|
470
476
|
'promotedFrom',
|
|
471
477
|
item,
|
|
472
478
|
);
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
* trigger surface — the forward seam the spec's Applied Answers q3 names.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
+
import {ACTION_PINS, pinnedUses} from './install-ci-action-pins.js';
|
|
24
25
|
import type {ResolvedCIConfig} from './install-ci-core.js';
|
|
26
|
+
import {shouldDropCheckoutCredentials} from './install-ci-core.js';
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
29
|
* The required-status-check CONTEXT install-ci sets via branch protection AND the
|
|
@@ -47,7 +49,18 @@ export const VERIFY_WORKFLOW_PATH = 'workflows/verify.yml';
|
|
|
47
49
|
* so the GitHub check it produces matches the required-status `context` install-ci
|
|
48
50
|
* configures on `main` — by construction, not by hand-typed agreement.
|
|
49
51
|
*/
|
|
50
|
-
export function generateVerifyWorkflow(
|
|
52
|
+
export function generateVerifyWorkflow(config: ResolvedCIConfig): string {
|
|
53
|
+
// `persist-credentials: false` ONLY on a repo KNOWN to be public (see
|
|
54
|
+
// {@link shouldDropCheckoutCredentials}): this job has `contents: read`, so
|
|
55
|
+
// the persisted token can only ever read, and on a public repo reads need
|
|
56
|
+
// no token. Unknown / private / internal ⇒ today's checkout, unchanged.
|
|
57
|
+
const checkoutCredentials = shouldDropCheckoutCredentials(config)
|
|
58
|
+
? `
|
|
59
|
+
# Public repository: every git read works without a token, and this
|
|
60
|
+
# job cannot push, so do not leave the token in .git/config for later
|
|
61
|
+
# steps (the project-setup hook's installs, the gate) to read.
|
|
62
|
+
persist-credentials: false`
|
|
63
|
+
: '';
|
|
51
64
|
return `\
|
|
52
65
|
# dorfl — the VERIFY workflow (Tier-1 GitHub ceiling; spec
|
|
53
66
|
# land-time-reverify-and-parallel-merge-ceiling, task
|
|
@@ -100,9 +113,9 @@ jobs:
|
|
|
100
113
|
${VERIFY_CHECK_CONTEXT}:
|
|
101
114
|
runs-on: ubuntu-latest
|
|
102
115
|
steps:
|
|
103
|
-
- uses:
|
|
116
|
+
- uses: ${pinnedUses(ACTION_PINS.checkout)}
|
|
104
117
|
with:
|
|
105
|
-
fetch-depth: 0
|
|
118
|
+
fetch-depth: 0${checkoutCredentials}
|
|
106
119
|
- uses: ./.github/actions/dorfl-setup
|
|
107
120
|
- name: run the repo verify gate
|
|
108
121
|
run: dorfl verify
|