Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions docs/session-context/T3-CODE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Optional session context: T3 Code

This file is an explicit, user-selected routing artifact for sessions that run
inside T3 Code and should keep following mstack's `/meta-mode` workflow. It
complements `SESSION-CONTEXT.md`; the mstack installer never registers it as a
hook, startup command, or implicit instruction.

## Opt-in contract

1. Select the `session-context` artifact explicitly with
`--artifact session-context`. This installs the whole `session-context/`
directory, so this file arrives next to `SESSION-CONTEXT.md`.
2. Configure the active harness to read this file using its documented,
user-owned session or project configuration surface.
3. Confirm the harness configuration and review the file before using it in a
sensitive repository.

If the file is not explicitly selected and configured, do nothing.

## When this applies

Apply this only when the `t3-code` MCP server's tools are actually present in
the session. T3 Code injects that server and its own orchestration instructions
into hosted provider sessions, and its tool names may carry a harness prefix such
as `mcp__t3_code__delegate_task`. T3 attaches the server lazily on some
harnesses, so if a tool scan shows nothing, make one bounded direct call to
`orchestrator_capabilities` before concluding the tools are absent.

If the tools are absent or that call fails, do nothing special and follow
`/meta-mode` normally, including its usual `run-role` and PR-watching paths.

## Routing inside T3 Code

These rules refine, and never replace, T3's own injected instructions and the
harness's normal approval and tool policy.

- Delegation: prefer `delegate_task` over spawning provider CLIs with
`run-role` for child work. Pick the provider and model from
`orchestrator_capabilities`, keep each returned `taskId`, and manage it with
`task_status` or `task_cancel`. For every further round, call `delegate_task`
again with the full brief, prior findings, and unresolved objections and a new
`clientRequestId`; do not continue a child with `t3_thread_send` on its
`childThreadId`.
- Threads: do not use `t3_thread_launch` or `create_threads` for a plain
"subagent" or parallel-work request. They create top-level conversations and
are for when the user explicitly asks for separate threads.
- Pull requests: call `link_pull_request` with the full URL for every PR you
create or work on, including each layer of a stack. When asked to watch or
babysit a PR, call `watch_pull_request` and end the turn instead of polling or
running the Bun PR watcher in `tools/meta-mode/watch-pr`; T3 wakes the thread
on check completion, new comments, or conflicts. Call `unwatch_pull_request`
before handing the work back to the user.

## When the task is T3 Code itself

If the repository being changed is T3 Code, its `AGENTS.md` applies and takes
precedence over this file. Its three hazards, because the user's own T3 instance
is often the one running you:

- Never kill a process by matching a name, path, or worktree string. Kill only a
PID you captured when you started it.
- Never start a server against, or write to, `~/.t3/userdata`. Copy data into
your worktree's own state instead.
- Never set `VITE_HTTP_URL` or `VITE_WS_URL` for development.

## Security and portability boundary

This is plain text, not executable configuration. It does not grant tools,
permissions, network access, or persistence, and it does not configure or
authenticate the `t3-code` MCP server; T3 Code owns that. Keep routing advisory:
do not execute commands, modify files, install hooks, or send data without the
harness's normal approval and tool policy. Hooks remain unsupported unless
explicitly configured outside mstack.

Do not put secrets, credentials, transcript contents, bearer tokens, or
machine-specific paths in this file. Keep the canonical skill tree
Harness-neutral; this artifact is a separate optional boundary for users who
choose session routing.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"install-skills": "node scripts/install.mjs",
"optimize-context": "node scripts/optimize-context.mjs",
"reconcile-context": "node scripts/reconcile-context.mjs",
"test": "node scripts/validate.mjs && node scripts/skill-baseline.mjs --check && node --test scripts/install.test.mjs scripts/install-migration.test.mjs scripts/migration-integration.test.mjs scripts/skill-discovery.test.mjs scripts/harness-targets.test.mjs scripts/remote-discovery.test.mjs scripts/context.test.mjs scripts/model-config.test.mjs scripts/model-budget.test.mjs scripts/audit-context.test.mjs scripts/worktree-audit.test.mjs tools/meta-mode/check-plan.test.mjs scripts/sync-upstream.test.mjs scripts/runtime.test.mjs scripts/environment.test.mjs scripts/skill-integrity.test.mjs scripts/skill-baseline.test.mjs scripts/version-integrity.test.mjs scripts/agent-format.test.mjs scripts/history.test.mjs scripts/harness-policy.test.mjs scripts/harness-project.test.mjs scripts/check-upstream-adaptations.test.mjs scripts/check-playbooks.test.mjs scripts/pack-claude-skills.test.mjs scripts/session-hook.test.mjs"
"test": "node scripts/validate.mjs && node scripts/skill-baseline.mjs --check && node --test scripts/install.test.mjs scripts/install-migration.test.mjs scripts/migration-integration.test.mjs scripts/skill-discovery.test.mjs scripts/harness-targets.test.mjs scripts/remote-discovery.test.mjs scripts/context.test.mjs scripts/model-config.test.mjs scripts/model-budget.test.mjs scripts/audit-context.test.mjs scripts/worktree-audit.test.mjs tools/meta-mode/check-plan.test.mjs scripts/sync-upstream.test.mjs scripts/runtime.test.mjs scripts/environment.test.mjs scripts/skill-integrity.test.mjs scripts/skill-baseline.test.mjs scripts/version-integrity.test.mjs scripts/agent-format.test.mjs scripts/history.test.mjs scripts/harness-policy.test.mjs scripts/harness-project.test.mjs scripts/check-upstream-adaptations.test.mjs scripts/check-playbooks.test.mjs scripts/pack-claude-skills.test.mjs scripts/session-hook.test.mjs scripts/skill-compat.test.mjs"
},
"bin": {
"mstack": "scripts/install.mjs",
Expand Down
148 changes: 148 additions & 0 deletions scripts/skill-compat-lib.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// T3 Code lists skills discovered by each provider. Cursor's and Antigravity's
// scanners drop a skill whose frontmatter fails YAML parsing, and T3's composer
// only forms a skill chip for names matching the pattern below. Keep these
// checks dependency-free: they inspect only the scalar forms a skill description
// or name can take (plain, quoted, block), not YAML in general.
//
// YAML separation whitespace is space and tab only, so every check below uses
// [ \t] and never \s (which also matches NBSP and other Unicode spaces that are
// ordinary plain-scalar text).
//
// Deliberately stricter than YAML: ` #` inside a plain scalar is a valid comment,
// not a parse error, but it silently truncates the value. A truncated
// description misroutes the skill, so it is rejected like a parse failure.

const T3_SKILL_NAME = /^[a-zA-Z0-9][a-zA-Z0-9:_-]*$/;
const PLAIN_SCALAR_FIELDS = ["name", "description"];
// Indicators that can never start a plain scalar (quote and block forms are handled separately).
// A leading `|` or `>` is a block header, handled before the plain-scalar check; otherwise invalid.
const ALWAYS_INDICATORS = new Set(["[", "]", "{", "}", ",", "&", "*", "!", "%", "@", "`", "|", ">"]);
// `-`, `?` and `:` are indicators only when followed by a separator.
const CONDITIONAL_INDICATORS = /^[-?:]([ \t]|$)/;
// Block scalar header: indentation and chomping indicators only, then an optional comment.
const BLOCK_HEADER = /^[|>](?:[1-9][+-]?|[+-][1-9]?)?(?:[ \t]+#.*)?$/;
// YAML 1.2 double-quoted escapes. A backslash before a newline is a line continuation.
const SIMPLE_ESCAPES = '0abtnvfre \t"/\\N_LP\n';
const HEX_ESCAPES = { x: 2, u: 4, U: 8 };
const COMMENT_TAIL = /^(?:[ \t]+#.*)?[ \t]*$/;
const BLANK_OR_COMMENT_LINE = /^[ \t]*(?:#.*)?$/;
const QUOTE_REMEDY = "quote the value";

const isCommentLine = (line) => /^[ \t]*#/.test(line);
const trimSeparators =(text) => text.replace(/^[ \t]+|[ \t]+$/g, "");

/** Reads a top-level field: its same-line value and any indented continuation lines. */
function readField(lines, field) {
const start = lines.findIndex((line) => new RegExp(`^${field}:([ \\t]|$)`).test(line));
if (start === -1) return undefined;
let first = trimSeparators(lines[start].slice(field.length + 1));
if (first.startsWith("#")) first = ""; // the whole remainder is a comment
const continuation = [];
for (const line of lines.slice(start + 1)) {
if (line.trim() === "") continue;
if (!/^[ \t]/.test(line)) break;
continuation.push(line);
}
return { first, continuation };
}

/** Parses a quoted scalar that may continue onto indented lines. */
function inspectQuoted(text) {
const quote = text[0];
let close = -1;
for (let i = 1; i < text.length && close === -1; i++) {
if (quote === '"' && text[i] === "\\") {
const escape = text[i + 1];
if (escape === undefined) break; // unterminated
const digits = HEX_ESCAPES[escape];
if (digits !== undefined) {
if (!new RegExp(`^[0-9a-fA-F]{${digits}}$`).test(text.slice(i + 2, i + 2 + digits))) {
return { problem: `has an invalid escape \\${escape} (needs ${digits} hex digits)` };
}
i += 1 + digits;
} else if (SIMPLE_ESCAPES.includes(escape)) i++;
else return { problem: `has an invalid escape \\${escape}; double-quoted YAML allows only the YAML 1.2 escapes` };
} else if (text[i] === quote) {
if (quote === "'" && text[i + 1] === "'") i++;
else close = i;
}
}
if (close === -1) return { problem: `has an unterminated ${quote === '"' ? "double" : "single"}-quoted scalar` };
const [sameLine, ...later] = text.slice(close + 1).split("\n");
if (!COMMENT_TAIL.test(sameLine) || !later.every((line) => BLANK_OR_COMMENT_LINE.test(line))) {
return { problem: `has content after the closing ${quote}` };
}
const inner = text.slice(1, close);
return { value: quote === "'" ? inner.replaceAll("''", "'") : inner };
}

/** Checks a block scalar header and that its body is indented under the (indent 0) key. */
function inspectBlock({ first, continuation }) {
if (!BLOCK_HEADER.test(first)) return { problem: `has an invalid block scalar header ${first}` };
if (continuation.length === 0) return { problem: "has an empty block scalar body" };
const indicators = first.match(/^[|>]([1-9+-]*)/)[1];
const explicit = Number(indicators.match(/[1-9]/)?.[0] ?? 0);
const indents = continuation.map((line) => line.match(/^ */)[0].length);
// Without an indentation indicator YAML takes the first body line's indent.
const required = Math.max(explicit || indents[0], 1);
const end = indents.findIndex((indent) => indent < required);
if (end === -1) return {};
// A line below the body's indent ends the scalar. Only comments may follow it,
// and a comment-only line there is not body text.
if (end > 0 && continuation.slice(end).every(isCommentLine)) return {};
return { problem: `has a block scalar body line indented less than ${required} space(s)` };
}

function inspectPlain(lines) {
const head = trimSeparators(lines[0]);
if (ALWAYS_INDICATORS.has(head[0]) || CONDITIONAL_INDICATORS.test(head)) {
return { problem: `starts with YAML indicator ${head[0]}; ${QUOTE_REMEDY}` };
}
const value = lines.map(trimSeparators).join(" ");
for (const line of lines) {
if (/:([ \t]|$)/.test(trimSeparators(line))) {
return { value, problem: `contains ': ' (invalid in a plain YAML scalar); ${QUOTE_REMEDY}` };
}
// Includes an indented `#` line inside a multiline plain scalar.
if (/(^|[ \t])#/.test(line)) {
return { value, problem: `contains ' #', which starts a YAML comment and truncates the value; ${QUOTE_REMEDY}` };
}
}
return { value };
}

/** Returns `{ problem }` and/or the scalar's string `value` when it can be determined. */
function inspectField({ first, continuation: rawContinuation }) {
// Comment lines between the key and its value are not part of the value.
const valueStart = rawContinuation.findIndex((line) => !isCommentLine(line));
const continuation = first === "" ? (valueStart === -1 ? [] : rawContinuation.slice(valueStart)) : rawContinuation;
const lines = [first, ...continuation].filter((line) => trimSeparators(line) !== "");
if (lines.length === 0) return { problem: "is empty or comment-only (YAML null)" };
const head = trimSeparators(lines[0]);
// A block header is the value itself, either after the key or on the next value-bearing line.
if (/^[|>]/.test(first)) return inspectBlock({ first, continuation });
if (first === "" && /^[|>]/.test(head)) {
return inspectBlock({ first: head, continuation: continuation.slice(1) });
}
if (/^["']/.test(head)) return inspectQuoted(lines.map(trimSeparators).join("\n"));
return inspectPlain(lines);
}

/**
* Returns problems that make T3-hosted providers drop or fail to chip a skill.
* Well-formed quoted and block scalars are accepted as written.
*/
export function skillCompatProblems(frontmatter) {
const lines = frontmatter.split(/\r?\n/);
const problems = [];
for (const fieldName of PLAIN_SCALAR_FIELDS) {
const field = readField(lines, fieldName);
if (!field) continue;
const { problem, value } = inspectField(field);
if (problem) problems.push(`${fieldName} ${problem}`);
if (fieldName === "name" && value && (!T3_SKILL_NAME.test(value) || !/[a-zA-Z]/.test(value))) {
problems.push(`name ${value} does not match the T3 skill chip pattern ${T3_SKILL_NAME}`);
}
}
return problems;
}
Loading
Loading