@taskless/cli 0.10.2 → 0.11.1

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.
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Public entry for `@taskless/cli/layout`.
3
+ *
4
+ * The rule layout table, as data: which engines exist, what file *is* the rule
5
+ * for each of them, where its per-engine config and capture rules live, and the
6
+ * directory names the whole tree hangs off. A rule is one directory,
7
+ * `.taskless/rules/<engine>/<id>/`, and this describes its contents.
8
+ *
9
+ * These are the same values the CLI itself dispatches on — not a copy kept in
10
+ * step, the actual module. Nothing here reaches the filesystem, the network,
11
+ * telemetry, or the command tree, so a Worker can import it; the build fails
12
+ * rather than emitting an entry whose graph reaches a host capability.
13
+ *
14
+ * ## Why this is published
15
+ *
16
+ * A service that builds a rule payload has to know what a complete rule is for
17
+ * a given engine. The alternative to importing it is transcribing it, and
18
+ * transcription drifts: the same layout was written into seven code comments
19
+ * and one specification here, all naming a path two migrations had already
20
+ * moved, and one of those stale comments carried the wrong layout into a
21
+ * cross-team design document before anyone noticed.
22
+ *
23
+ * ## What it does not tell you
24
+ *
25
+ * Only the shape. Whether a rule *runs* is a separate question with separate
26
+ * answers — a runtime rule executes only against a server-blessed signature
27
+ * over its `check.ts`, and a Vale rule fires only where its own `.vale.ini`
28
+ * scopes it. A file set that satisfies this table is well-formed, not
29
+ * necessarily live.
30
+ */
31
+ export { ENGINES, ENGINE_LAYOUTS, RULES_DIRECTORY, RULE_TESTS_DIRECTORY, TASKLESS_DIRECTORY, isKnownEngine, type EngineExecutor, type EngineLayout, type EngineName, type FixtureLayout, } from "../rules/layout.js";
package/dist/layout.js ADDED
@@ -0,0 +1,43 @@
1
+ const t = ["sg", "vale", "runtime"], r = "rules", i = ".taskless", n = ".tests", s = {
2
+ sg: {
3
+ engine: "sg",
4
+ ruleFile: (e) => `${e}.yml`,
5
+ ruleConfigFile: void 0,
6
+ capturesDirectory: void 0,
7
+ fixtureLayout: "ast-grep-test",
8
+ executor: "ast-grep"
9
+ },
10
+ vale: {
11
+ engine: "vale",
12
+ ruleFile: (e) => `${e}.yml`,
13
+ ruleConfigFile: ".vale.ini",
14
+ capturesDirectory: void 0,
15
+ fixtureLayout: "case-documents",
16
+ executor: "vale-runner"
17
+ },
18
+ runtime: {
19
+ engine: "runtime",
20
+ ruleFile: () => "check.ts",
21
+ ruleConfigFile: void 0,
22
+ // `captures/` rather than `matchers/`: "matcher" denotes a Vale `[<glob>]`
23
+ // config section elsewhere in this tree, and one word for two unrelated
24
+ // concepts is a cost paid at every future reading.
25
+ capturesDirectory: "captures",
26
+ // Directories, not documents. A runtime rule exists because its evidence
27
+ // spans more than one file, so a one-file case could not express the rules
28
+ // this tier is for.
29
+ fixtureLayout: "case-directories",
30
+ executor: "runtime-harness"
31
+ }
32
+ };
33
+ function u(e) {
34
+ return t.includes(e);
35
+ }
36
+ export {
37
+ t as ENGINES,
38
+ s as ENGINE_LAYOUTS,
39
+ r as RULES_DIRECTORY,
40
+ n as RULE_TESTS_DIRECTORY,
41
+ i as TASKLESS_DIRECTORY,
42
+ u as isKnownEngine
43
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Public entry for `@taskless/cli/node/runtimes`.
3
+ *
4
+ * Where the engine binaries this CLI executes actually live on this host, and
5
+ * how to find them. It is the CLI's own resolver and the CLI's own package
6
+ * specs — not a copy kept in step, the actual modules — so a consumer that
7
+ * installs this package resolves the same ast-grep and the same Vale that
8
+ * `taskless check` would run.
9
+ *
10
+ * ## Why this is published
11
+ *
12
+ * A service that verifies a generated rule has to execute it, and a rule's
13
+ * behaviour is a property of the engine version that runs it, not of the
14
+ * version it was written against. ast-grep in particular changes what a capture
15
+ * matches across minors: a rule verified under one and executed under another
16
+ * can verify clean and then match nothing, which is a rule that passes its gate
17
+ * and never fires. Nothing reports that, because both sides succeeded.
18
+ *
19
+ * The alternative to importing this is pinning the engine version by hand
20
+ * alongside the CLI version, in a second place, and keeping the two in step.
21
+ * They drift, and the drift is silent in exactly the direction that matters.
22
+ * Installing this package and asking it where the binary is makes the engine
23
+ * version a consequence of the CLI version rather than a parallel fact: the
24
+ * pinned platform packages arrive as this package's own optional dependencies,
25
+ * and they move when the pin moves.
26
+ *
27
+ * ## This entry requires Node, and says so in its path
28
+ *
29
+ * `node/` is in the specifier deliberately. Resolution spawns each candidate to
30
+ * make it identify itself, reads the filesystem, and consults `PATH`, so this
31
+ * graph reaches `node:child_process`, `node:fs`, `node:module`, `node:path` and
32
+ * `node:url`. It will not run on a Worker or in a browser, and the import path
33
+ * says that before a build error has to.
34
+ *
35
+ * This is the difference between this entry and `@taskless/cli/prompts` and
36
+ * `@taskless/cli/layout`, which are host-free and whose builds fail if their
37
+ * graphs ever reach a host capability. Every published entry is classified as
38
+ * one or the other in `vite.config.ts`, and the build fails on an export
39
+ * belonging to neither, so this exemption is a declaration rather than an
40
+ * omission.
41
+ *
42
+ * ## Resolution reports a miss as a value, and each side decides what it means
43
+ *
44
+ * {@link resolvePlatformBinary} returns `{ path: undefined, tried }` when no
45
+ * candidate verified. It does not throw, and that is deliberate rather than
46
+ * incidental: `taskless check` runs several engines, and a host without Vale
47
+ * installed must lose the Vale rules rather than the whole run. Encoding "not
48
+ * found" as a value is what lets a caller choose.
49
+ *
50
+ * A verifier should choose the opposite. A sandbox whose optional dependency
51
+ * did not install resolves `undefined` for the same reason a developer's laptop
52
+ * does — nothing is there — but the consequence is not a skipped engine, it is
53
+ * a verification that ran no engine and reported success. Fail closed on
54
+ * `undefined`, and use `tried` to say where you looked:
55
+ *
56
+ * ```ts
57
+ * import {
58
+ * AST_GREP_BINARY,
59
+ * resolvePlatformBinary,
60
+ * } from "@taskless/cli/node/runtimes";
61
+ *
62
+ * const { path, tried } = resolvePlatformBinary(AST_GREP_BINARY);
63
+ * if (path === undefined) {
64
+ * throw new Error(`ast-grep not resolved. Looked in: ${tried.join(", ")}`);
65
+ * }
66
+ * ```
67
+ *
68
+ * ## What a resolved path is, and is not
69
+ *
70
+ * It is a path that exists and that answered `--version` with output matching
71
+ * the spec's `identity`, so it is the binary and not a placeholder a failed
72
+ * install left behind.
73
+ *
74
+ * **It is not a promise about which version answered.** The first candidate
75
+ * tried is the pinned platform package, but a host that only has the engine on
76
+ * `PATH` resolves that instead, and that binary can be any version.
77
+ *
78
+ * `resolution.source` says which tier answered. Only `"platform-package"` is
79
+ * the pinned install, so only that value tells a caller the binary is the one
80
+ * this CLI ships:
81
+ *
82
+ * ```ts
83
+ * const { path, source } = resolvePlatformBinary(AST_GREP_BINARY);
84
+ * if (path === undefined) throw new Error("no engine");
85
+ * if (source !== "platform-package") throw new Error("not the pinned engine");
86
+ * // The binary at `path` is now known to be the pinned build.
87
+ * ```
88
+ *
89
+ * {@link isPlatformBinary} does NOT answer this. It checks that a file exists
90
+ * and answers `--version` as the right tool, which every tier satisfies by the
91
+ * time a path is returned. Identity is not provenance, and an earlier draft of
92
+ * this note said otherwise.
93
+ *
94
+ * **The pinned versions are deliberately not published here.** A consumer
95
+ * wanting to know it has the pinned engine should read `source`, which answers
96
+ * that directly and at resolution time. A version constant would answer it
97
+ * later and less well, by inviting a comparison against a number rather than a
98
+ * check of where the binary came from.
99
+ */
100
+ export { findOnPath, isPlatformBinary, pathCommandName, platformPackageName, resolvePlatformBinary, type PlatformBinaryResolution, type PlatformBinarySource, type PlatformBinarySpec, } from "../../rules/platform-binary.js";
101
+ export { AST_GREP_BINARY } from "../../rules/ast-grep-binary.js";
102
+ export { VALE_BINARY } from "../../rules/vale/binary.js";
@@ -0,0 +1,10 @@
1
+ import { A as s, V as m, f as o, i as e, p as f, a as t, r as i } from "../binary-DSACXL6n.js";
2
+ export {
3
+ s as AST_GREP_BINARY,
4
+ m as VALE_BINARY,
5
+ o as findOnPath,
6
+ e as isPlatformBinary,
7
+ f as pathCommandName,
8
+ t as platformPackageName,
9
+ i as resolvePlatformBinary
10
+ };
@@ -0,0 +1,103 @@
1
+ import { type RecipeOptions, type RecipeText } from "./recipes.js";
2
+ /**
3
+ * Public entry for `@taskless/cli/prompts`.
4
+ *
5
+ * Everything here renders through the same embedded recipe text and the same
6
+ * render path `taskless agent <topic>` serves, so the two surfaces cannot emit
7
+ * different guidance. Nothing in this graph reaches the CLI runtime: no citty
8
+ * command tree, no telemetry, no filesystem or network, so a Worker can import
9
+ * it without dragging the CLI in behind it.
10
+ */
11
+ /**
12
+ * Topics exported as public API. Hand-maintained rather than derived from the
13
+ * recipe files, because an exported name is a promise held for a major version
14
+ * and a new `agent/*.md` must not be able to publish one by existing. The
15
+ * completeness check in `test/prompts.test.ts` asserts this list plus
16
+ * {@link INTERNAL_TOPICS} accounts for every canonical recipe on disk.
17
+ *
18
+ * The list starts at what a consumer has actually asked for and grows on
19
+ * demand. It is the authoring recipe for each engine a rule can be routed to,
20
+ * so a consumer that can decide a rule belongs to an engine can also reach the
21
+ * procedure for writing one. Exporting a chooser without its destinations
22
+ * reproduces, for the platform generator, the dead end this surface exists to
23
+ * remove.
24
+ *
25
+ * `engine-selection` used to be exported alongside them. It no longer exists:
26
+ * the criterion it carried now lives in `route`, stated once.
27
+ *
28
+ * `route` was withheld on the grounds that it still contains local mechanics
29
+ * (`taskless detect`, on-device authoring) a Worker cannot run, and that a
30
+ * consumer could adjudicate ambiguous calls from the three destinations. Both
31
+ * halves were wrong in the same way.
32
+ *
33
+ * The mechanics are true and do not defeat it: a service consumer ignores
34
+ * them, which is a smaller adaptation than restating the criteria. And
35
+ * adjudicating from the destinations is what the platform generator actually
36
+ * tried. It hand-wrote the same judgement, arrived at `static | runtime` with
37
+ * nowhere to put `vale`, and generated every prose rule as an ast-grep rule
38
+ * while its own delivery layer could already serve a Vale one. `route` warns
39
+ * about exactly that conflation: whether a rule is static or runtime is one
40
+ * axis, and which engine authors it is another.
41
+ *
42
+ * That is the dead end named two paragraphs up, mirrored. Exporting a chooser
43
+ * without its destinations strands a consumer that can route but not author;
44
+ * exporting destinations without the chooser strands one that can author but
45
+ * not route, and it will write its own chooser rather than stop.
46
+ */
47
+ export declare const TOPICS: readonly ["create-sg-rule", "create-vale-rule", "create-runtime-rule", "route"];
48
+ /**
49
+ * Recipes deliberately withheld from the export, recorded so they stay visible
50
+ * decisions rather than oversights. Two groups:
51
+ *
52
+ * - Command recipes (`auth` … `update`) walk an agent through running a CLI
53
+ * subcommand on a developer's machine. There is no caller for them outside
54
+ * the CLI that hosts those commands.
55
+ * - Authoring recipes are unreachable server-side: `create-remote-rule` states
56
+ * the boundary from the client's side, `detect` documents a CLI subprocess a
57
+ * Worker cannot spawn, `create-legacy-rule` targets a local toolchain, and
58
+ * `rule-meta` describes a local sidecar file the CLI never writes.
59
+ */
60
+ export declare const INTERNAL_TOPICS: readonly ["auth", "check", "ci", "create-legacy-rule", "create-remote-rule", "delete-rule", "detect", "improve-rule", "info", "init", "onboard", "rule", "rule-meta", "update", "verify-rule"];
61
+ /** A topic name the package exports. Unknown names fail to type-check. */
62
+ export type PromptTopic = (typeof TOPICS)[number];
63
+ /** Options accepted by every prompt render function. */
64
+ export type PromptOptions = RecipeOptions;
65
+ /**
66
+ * Render a prompt to finished text. Every `%(KEY)s` placeholder is resolved
67
+ * from values the package already holds, so the caller never handles a
68
+ * template dialect.
69
+ *
70
+ * @throws when the topic has no canonical recipe in the build, which means
71
+ * {@link TOPICS} and the recipe files have diverged.
72
+ */
73
+ export declare function getPrompt(topic: PromptTopic, options?: PromptOptions): string;
74
+ /**
75
+ * A prompt's text together with the sprintf variable names its template
76
+ * contains. The names come from `sprintf-js`'s own parse, not from a pattern
77
+ * match over the text.
78
+ */
79
+ export type Instructions = RecipeText;
80
+ /**
81
+ * Render a prompt and report which variables its template carries.
82
+ *
83
+ * `text` is byte-identical to {@link getPrompt} for the same arguments; the
84
+ * addition is `variables`, which tells a consumer what this topic's template
85
+ * was parameterized by without making them parse it.
86
+ *
87
+ * @throws when the topic has no canonical recipe in the build.
88
+ */
89
+ export declare function getInstructions(topic: PromptTopic, options?: PromptOptions): Instructions;
90
+ /**
91
+ * The **unrendered** template for a prompt, plus the variables it contains.
92
+ *
93
+ * Use this when the host knows a value the package cannot: which launcher the
94
+ * reader will actually use, which package manager the target repository runs.
95
+ * Render it with `sprintf-js`'s named-argument form; the text is the source
96
+ * template verbatim, so its `%%` escapes are intact and it is safe to render
97
+ * exactly once.
98
+ *
99
+ * @throws when the topic has no canonical recipe in the build.
100
+ */
101
+ export declare function getRawInstructions(topic: PromptTopic, options?: PromptOptions): Instructions;
102
+ /** Every exported topic as a render function, keyed by topic name. */
103
+ export declare const PROMPTS: Record<PromptTopic, (options?: PromptOptions) => string>;
@@ -0,0 +1,114 @@
1
+ /** The canonical `<topic>.md` recipe names present in the build. */
2
+ export declare function canonicalRecipeTopics(): string[];
3
+ /** Options accepted by the shared render path. */
4
+ export interface RecipeOptions {
5
+ /**
6
+ * Select the `.anonymous` variant of the topic, falling back to the
7
+ * canonical recipe when the topic has no variant.
8
+ *
9
+ * @default false
10
+ */
11
+ anonymous?: boolean;
12
+ /**
13
+ * Value substituted for the `%(PACKAGE_MANAGER_DLX)s` placeholder. The
14
+ * default is an agent-fill marker, which is the right answer whenever
15
+ * the caller does not know the consuming repo's package manager.
16
+ *
17
+ * @default "<package-manager-dlx>"
18
+ */
19
+ packageManagerDlx?: string;
20
+ /**
21
+ * Value substituted for the `%(TASKLESS_CLI)s` placeholder: the full command
22
+ * a reader would type to run this CLI, launcher and package specifier
23
+ * included (`npx @taskless/cli@latest`, `pnpm dlx @taskless/cli-nightly@…`).
24
+ *
25
+ * THIS IS AN ARGUMENT, NEVER AN AMBIENT READ. Detecting the launcher needs
26
+ * `process.argv` and `process.env`, and this module is imported by Workers
27
+ * without `nodejs_compat`, where a module-scope `process` read throws at
28
+ * import time. `assert-prompts-graph` in `vite.config.ts` would not catch it
29
+ * either — `process` is a global, not an import — so the constraint is kept
30
+ * by shape: the CLI detects and passes the value in (see
31
+ * `src/util/package-manager.ts`), and a host that imports
32
+ * `@taskless/cli/prompts` passes nothing and gets the marker.
33
+ *
34
+ * Omitting it falls back to this build's own invocation when the build is
35
+ * not prod, and to the agent-fill marker otherwise.
36
+ *
37
+ * @default "<taskless-cli>"
38
+ */
39
+ invocation?: string;
40
+ /**
41
+ * Render the steps that gather evidence by running this CLI.
42
+ *
43
+ * `false` replaces them with a statement of what the caller must supply
44
+ * instead. It exists because `invocation` cannot do this job: that option
45
+ * substitutes the BINARY NAME inside a command, so a consumer with no CLI
46
+ * setting it to a phrase renders `Run: <no CLI available> detect --json` — a
47
+ * malformed instruction rather than a clean absence, and worse than either
48
+ * honest answer.
49
+ *
50
+ * The evidence itself is not optional. A consumer that cannot run the
51
+ * commands still needs the linters, languages, rule styles, login state and
52
+ * owner, because the routing criteria are stated in terms of them. So this
53
+ * names what to supply rather than dropping the steps.
54
+ *
55
+ * @default true
56
+ */
57
+ mechanics?: boolean;
58
+ /**
59
+ * Include the `# Topic: <name> (CLI v<version> / topic vN)` first line.
60
+ * Suppressing it drops the CLI version from the text, which matters to
61
+ * an LLM consumer whose prompt-cache key would otherwise churn on every
62
+ * CLI publish.
63
+ *
64
+ * @default true
65
+ */
66
+ header?: boolean;
67
+ }
68
+ export declare function buildVariables(content: string, topic: string, options?: RecipeOptions): Record<string, string>;
69
+ /** A recipe's text plus the sprintf variables its template contains. */
70
+ export interface RecipeText {
71
+ text: string;
72
+ variables: string[];
73
+ }
74
+ /**
75
+ * Look up an agent recipe topic from the embedded recipe map and return the rendered
76
+ * text. Anonymous variants are preferred when `anonymous` is set and a
77
+ * variant exists; otherwise the canonical recipe is returned. Returns
78
+ * `undefined` when the topic is unknown.
79
+ */
80
+ export declare function getRecipe(topic: string, options?: RecipeOptions): string | undefined;
81
+ /**
82
+ * The **unrendered** template for a topic, plus the variables it contains.
83
+ *
84
+ * `text` is the source recipe with the build-target invocation rewrite applied
85
+ * and nothing else. The rewrite belongs here: it is build-target substitution
86
+ * rather than templating, and omitting it would make the raw text render to
87
+ * something the CLI never emits. Every `%(KEY)s` is left standing so a host
88
+ * that knows a value this package cannot know — its own launcher, its own
89
+ * package manager — can render the text itself.
90
+ *
91
+ * Returns `undefined` for an unknown topic, matching {@link getRecipe}. The
92
+ * public accessors in `./index.ts` turn that into a throw.
93
+ */
94
+ export declare function getRawRecipe(topic: string, options?: RecipeOptions): RecipeText | undefined;
95
+ /** A topic's rendered text plus the variables its template contains. */
96
+ export declare function getRenderedRecipe(topic: string, options?: RecipeOptions): RecipeText | undefined;
97
+ /**
98
+ * Remove Vale's in-file directives from a recipe before anyone reads it.
99
+ *
100
+ * A recipe is checked by this repository's own Vale rules, and two of them
101
+ * teach through a worked example that quotes the words a shipped rule flags.
102
+ * `<!-- vale no-hedging.no-hedging = NO -->` marks that example so the rest of
103
+ * the file stays covered, which is only possible because a recipe is markdown.
104
+ *
105
+ * The directives are configuration, not content. Stripping happens HERE, where
106
+ * the embedded files are read into the maps, rather than at any of the three
107
+ * render entry points: one place to be correct, and `getRawRecipe` is covered
108
+ * by the same stroke as the rendered paths.
109
+ *
110
+ * The whole line goes, including its newline. Leaving a blank line behind
111
+ * would change the markdown a reader sees, which would make the exclusion
112
+ * mechanism visible in the output it exists to keep clean.
113
+ */
114
+ export declare function stripValeDirectives(content: string): string;
@@ -0,0 +1,53 @@
1
+ import { g as n, a as i, b as u } from "./recipes-w68swLg4.js";
2
+ const o = [
3
+ "create-sg-rule",
4
+ "create-vale-rule",
5
+ "create-runtime-rule",
6
+ "route"
7
+ ], s = [
8
+ "auth",
9
+ "check",
10
+ "ci",
11
+ "create-legacy-rule",
12
+ "create-remote-rule",
13
+ "delete-rule",
14
+ "detect",
15
+ "improve-rule",
16
+ "info",
17
+ "init",
18
+ "onboard",
19
+ "rule",
20
+ "rule-meta",
21
+ "update",
22
+ "verify-rule"
23
+ ];
24
+ function a(e, r) {
25
+ return t(n(e, r), e);
26
+ }
27
+ function l(e, r) {
28
+ return t(i(e, r), e);
29
+ }
30
+ function d(e, r) {
31
+ return t(u(e, r), e);
32
+ }
33
+ function t(e, r) {
34
+ if (e === void 0)
35
+ throw new Error(
36
+ `No recipe is embedded for prompt topic "${r}". This is a packaging fault: TOPICS lists a topic with no agent/${r}.md behind it.`
37
+ );
38
+ return e;
39
+ }
40
+ const g = Object.fromEntries(
41
+ o.map((e) => [
42
+ e,
43
+ (r) => a(e, r)
44
+ ])
45
+ );
46
+ export {
47
+ s as INTERNAL_TOPICS,
48
+ g as PROMPTS,
49
+ o as TOPICS,
50
+ l as getInstructions,
51
+ a as getPrompt,
52
+ d as getRawInstructions
53
+ };