tspl-cli 0.1.3 → 0.1.5
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/README.md
CHANGED
|
@@ -28,7 +28,7 @@ npm i -g tspl-cli → tspl setup → tspl init → tspl task new "…" → tspl
|
|
|
28
28
|
|
|
29
29
|
## Requirements
|
|
30
30
|
|
|
31
|
-
- **Node
|
|
31
|
+
- **Node 20 or newer** (`node --version`)
|
|
32
32
|
- **git**
|
|
33
33
|
- **A TSPL account.** Ask your organization's owner or admin to add you. You get a temporary password and choose your own at the first login.
|
|
34
34
|
- **An AI tool** you are signed in to with your own subscription: Claude Code, Codex CLI, Gemini CLI, Antigravity, Cursor or VS Code Copilot
|
|
@@ -129,7 +129,7 @@ tspl init
|
|
|
129
129
|
`tspl init` does the following:
|
|
130
130
|
|
|
131
131
|
1. **Links the folder to a TSPL project.** It recognizes your repositories by their git history, so folder names and git hosts don't matter. If the project doesn't exist yet, it offers to create it (owner, admin or PM only). If it finds new repositories, it offers to add them.
|
|
132
|
-
2. **Finds your checks.** It reads your package scripts, without running them, and asks you to confirm which ones are the real **test**, **lint**, **typecheck** and **build** commands, and which are required.
|
|
132
|
+
2. **Finds your checks.** It reads your package scripts, without running them, and asks you to confirm which ones are the real **test**, **lint**, **typecheck** and **build** commands, and which are required. A test check is optional: without one, each done-when item is checked by a person (`tspl task verify`), or you finish the task with `tspl task done`.
|
|
133
133
|
3. **Asks about pull requests:** the GitHub repository, the target branch and the CI policy.
|
|
134
134
|
4. **Offers browser tests.** For web apps without browser tests, it offers to set up Playwright. Skip this with `--no-e2e`.
|
|
135
135
|
5. **Saves the shared project files** in `.tspl/`.
|
|
@@ -326,11 +326,14 @@ tspl build push t-xxxx # push the branch of every repo on the plan
|
|
|
326
326
|
```bash
|
|
327
327
|
tspl build stop t-xxxx # stop and lift the edit lock (branches are kept)
|
|
328
328
|
tspl plan amend t-xxxx # cancel the stopped run so a new plan version can be made
|
|
329
|
+
tspl task done t-xxxx --note "tested by hand on staging" # mark it done yourself
|
|
329
330
|
tspl task delete t-xxxx --reason "duplicate"
|
|
330
331
|
```
|
|
331
332
|
|
|
332
333
|
Use `stop` and `amend` only when the plan really has to change, not for every failure. A new plan version needs new approvals.
|
|
333
334
|
|
|
335
|
+
`tspl task done` finishes an approved task by hand, even if checks, tests, review or the pull request did not run. Any contributor can use it; a note is required. The task history keeps who marked it done, the note, and what was still open; the console shows the skipped steps as skipped. Stop a running build first (`tspl build stop`).
|
|
336
|
+
|
|
334
337
|
### Build by hand instead of `task run`
|
|
335
338
|
|
|
336
339
|
```bash
|
|
@@ -435,6 +438,7 @@ The steps are `plan`, `build`, `verify` and `bulk`. The engines are `claude`, `c
|
|
|
435
438
|
| `tspl task choose` | choose an approach before planning |
|
|
436
439
|
| `tspl task run` / `watch` / `status` | build a task, follow it, see where it is |
|
|
437
440
|
| `tspl task verify` | record a check you did by hand |
|
|
441
|
+
| `tspl task done` | mark an approved task done by hand, with a note |
|
|
438
442
|
| `tspl plan list` / `show` / `approve` / `reject` / `amend` | review and decide on plans |
|
|
439
443
|
| `tspl build start` / `status` / `check` / `push` / `stop` | manual build commands |
|
|
440
444
|
| `tspl member ...` | people and roles |
|
|
@@ -469,7 +473,7 @@ tspl task run --help
|
|
|
469
473
|
| `You are logged in to …, not …` | run `tspl login --api https://aitool-api.techivies.com` |
|
|
470
474
|
| Login expired | run `tspl login` |
|
|
471
475
|
| Forgot your password | run `tspl forgot-password --email you@company.com` |
|
|
472
|
-
| `tspl needs Node
|
|
476
|
+
| `tspl needs Node 20 or newer` | install Node 20+ from https://nodejs.org, then reinstall tspl |
|
|
473
477
|
| `/tspl` doesn't show in your AI tool | run `tspl connect <tool>` and restart the tool |
|
|
474
478
|
| `plan approve` refuses to run | run it in a normal terminal, not inside an AI tool |
|
|
475
479
|
| Build won't start: uncommitted changes | commit or stash your work, then run again |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tspl-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "TSPL command-line tool: learns your code, plans changes with you, builds, tests and opens pull requests",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"tspl",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"README.md"
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=
|
|
30
|
+
"node": ">=20"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@babel/parser": "^7.29.9",
|
|
34
|
-
"@inquirer/prompts": "^
|
|
34
|
+
"@inquirer/prompts": "^7.10.1",
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
36
|
-
"commander": "^
|
|
36
|
+
"commander": "^14.0.3",
|
|
37
37
|
"zod": "^4.6.5"
|
|
38
38
|
},
|
|
39
39
|
"scripts": {},
|
|
@@ -365,6 +365,21 @@ const verify = async (id, options) => {
|
|
|
365
365
|
console.log(`Recorded: done-when ${criterion} checked by you on ${workflow.heads.map(head => `${head.name}@${String(head.headSha).slice(0, 7)}`).join(', ')}. Continue: tspl task run ${id}`)
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
+
const markDone = async (id, options) => {
|
|
369
|
+
const { project } = await findProject(process.cwd())
|
|
370
|
+
const task = await request('GET', taskUrl(project._id, id))
|
|
371
|
+
const open = task.workflow ? `stage ${task.workflow.stage}` : `status ${task.status}`
|
|
372
|
+
if (!options.yes && !(await confirm({ message: `Mark ${id} done by hand (now ${open})? Checks that did not run stay recorded as skipped.`, default: false }))) {
|
|
373
|
+
console.log('Nothing changed.')
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
const { task: done, workflow } = await request('POST', `${taskUrl(project._id, id)}/workflow/done`, { note: options.note })
|
|
377
|
+
require('../workflow/runtime').finishLocal(id)
|
|
378
|
+
const problems = (workflow && workflow.manualCompletion && workflow.manualCompletion.problems) || []
|
|
379
|
+
console.log(`${id} is ${done.status}, marked by you: "${options.note}".`)
|
|
380
|
+
if (problems.length) console.log(`Still open when it was marked done (kept in the task history):\n${problems.map(item => ` - ${item}`).join('\n')}`)
|
|
381
|
+
}
|
|
382
|
+
|
|
368
383
|
const remove = async (id, options) => {
|
|
369
384
|
const { project } = await findProject(process.cwd())
|
|
370
385
|
if (!options.yes && !(await confirm({ message: `Delete task ${id} for everyone? It disappears from the console and from every list.`, default: false }))) {
|
|
@@ -465,6 +480,12 @@ module.exports = {
|
|
|
465
480
|
.option('--note <text>', 'what you looked at')
|
|
466
481
|
.option('--file <paths...>', 'screenshots or a screen recording that show it (png, jpg, webp, webm, mp4)')
|
|
467
482
|
.action(verify)
|
|
483
|
+
task
|
|
484
|
+
.command('done <task>')
|
|
485
|
+
.description('Mark an approved task done by hand, even if checks, tests or delivery did not run (kept in its history with your note)')
|
|
486
|
+
.requiredOption('--note <text>', 'why it is done, e.g. "tested by hand on staging"')
|
|
487
|
+
.option('-y, --yes', 'do not ask to confirm')
|
|
488
|
+
.action(markDone)
|
|
468
489
|
task
|
|
469
490
|
.command('scope <task>')
|
|
470
491
|
.description('Confirm a task scope as PM, or send it back with --back --note "…"')
|
package/src/project-init.js
CHANGED
|
@@ -219,7 +219,7 @@ const readiness = (config, localPaths) => {
|
|
|
219
219
|
const problems = []
|
|
220
220
|
const warnings = []
|
|
221
221
|
if (!config.checks.some(check => check.required && TEST_CATEGORIES.includes(check.category))) {
|
|
222
|
-
|
|
222
|
+
warnings.push('No required test check is configured: done-when items without a runnable test need a person (tspl task verify), or finish with tspl task done.')
|
|
223
223
|
}
|
|
224
224
|
config.repos.forEach(repo => {
|
|
225
225
|
if (!localPaths.has(repo.name)) problems.push(`Repo ${repo.name} is not cloned on this machine.`)
|
package/src/runtime-check.js
CHANGED
|
@@ -11,8 +11,14 @@ const workflowProgress = (task, at = new Date()) => {
|
|
|
11
11
|
if (!wf) return null
|
|
12
12
|
const current = effectiveStage(wf), position = wf.stage === 'ready' ? STAGES.length : STAGES.indexOf(current)
|
|
13
13
|
const options = task.plan?.workflow?.options || {}
|
|
14
|
+
const manual = wf.stage === 'ready' && wf.manualCompletion
|
|
14
15
|
const steps = STAGES.map((stage, i) => {
|
|
15
16
|
const saved = wf.stageResults?.[stage] || {}
|
|
17
|
+
if (manual) {
|
|
18
|
+
const done = saved.status === 'done'
|
|
19
|
+
const by = manual.userName || 'a person'
|
|
20
|
+
return { stage, label: LABELS[stage], status: done ? 'done' : 'skipped', reason: done ? saved.reason || `${LABELS[stage]} completed.` : `Skipped: marked done by ${by} (${manual.note}).`, startedAt: saved.startedAt || null, finishedAt: saved.finishedAt || null, command: null }
|
|
21
|
+
}
|
|
16
22
|
let status = i < position ? 'done' : i === position ? 'running' : 'pending'
|
|
17
23
|
let reason = status === 'done' ? `${LABELS[stage]} completed; workflow advanced.` : status === 'pending' ? `Waiting for ${LABELS[STAGES[i - 1]] || 'plan approval'} to complete.` : `${LABELS[stage]} is running.`
|
|
18
24
|
if (stage === 'reviewing' && options.review === false || ['awaiting-delivery', 'creating-pr', 'awaiting-ci'].includes(stage) && options.pullRequest === false) { status = 'skipped'; reason = 'Not required by this task’s approved workflow.' }
|
package/src/shared/workflow.js
CHANGED
|
@@ -212,9 +212,6 @@ const validateSnapshot = snapshot => {
|
|
|
212
212
|
if (ids.has(check.id)) throw new Error(`Duplicate check id "${check.id}"`)
|
|
213
213
|
ids.add(check.id)
|
|
214
214
|
})
|
|
215
|
-
if (!checks.some(check => check.required && TEST_CATEGORIES.includes(check.category))) {
|
|
216
|
-
throw new Error('The project has no required test check; add one to .tspl/project.json before planning a tested pull request')
|
|
217
|
-
}
|
|
218
215
|
const acceptance = snapshot.acceptance === undefined ? 0 : snapshot.acceptance
|
|
219
216
|
const manualCriteria = snapshot.manualCriteria === undefined ? [] : snapshot.manualCriteria
|
|
220
217
|
if (!Number.isInteger(acceptance) || acceptance < 0) throw new Error('acceptance must be a count')
|
|
@@ -380,7 +380,7 @@ const runTask = async ({ cwd, taskId, signal, options = {} }, deps) => {
|
|
|
380
380
|
if (problems.length) {
|
|
381
381
|
const manual = problems.filter(text => /Acceptance criterion/.test(text))
|
|
382
382
|
if (manual.length === problems.length) {
|
|
383
|
-
return outcome('reviewing', manual, snapshot.manualCriteria.map(n => `tspl task verify ${taskKey} --check ${n}`).concat([`Then: ${again}`]))
|
|
383
|
+
return outcome('reviewing', manual, snapshot.manualCriteria.map(n => `tspl task verify ${taskKey} --check ${n}`).concat([`Then: ${again}`, `Or finish by hand: tspl task done ${taskKey} --note "…"`]))
|
|
384
384
|
}
|
|
385
385
|
return block('other', problems.join(' '), [`Fix it, then: ${again}`])
|
|
386
386
|
}
|