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
7 changes: 7 additions & 0 deletions .changeset/forty-heads-fly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/intent': patch
---

Fix `intent list` suppressing the allow-all risk warning.

When `intent.skills` is set to allow-all (`"*"`), `intent list --no-notices` and `INTENT_NO_NOTICES=1` no longer hide the warning that all skill sources are permitted. This banner is a security-relevant signal, not a migration tip, so it's now excluded from suppression while other notices remain suppressible as before.
2 changes: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const config = [
{
name: 'intent/static-discovery',
files: [
'packages/intent/src/scanner.ts',
'packages/intent/src/discovery/scanner.ts',
'packages/intent/src/lockfile.ts',
'packages/intent/src/manifest.ts',
'packages/intent/src/mcp/**/*.ts',
Expand Down
3 changes: 2 additions & 1 deletion packages/intent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"test:smoke": "pnpm run build && node dist/cli.mjs --help > /dev/null && node dist/cli.mjs load --help > /dev/null",
"test:lib": "vitest run --exclude 'tests/integration/**'",
"test:integration": "vitest run tests/integration/",
"test:types": "tsc --noEmit"
"test:types": "tsc --noEmit",
"test:eslint": "eslint ."
}
}
4 changes: 2 additions & 2 deletions packages/intent/src/commands/exclude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,11 @@ function printExcludes(excludes: Array<string>, json?: boolean): void {
}
}

export async function runExcludeCommand(
export function runExcludeCommand(
actionArg: string | undefined,
patternArg: string | undefined,
options: ExcludeCommandOptions,
): Promise<void> {
): void {
const action = normalizeAction(actionArg)
const cwd = process.cwd()
const pkg = readPackageJson(cwd)
Expand Down
6 changes: 3 additions & 3 deletions packages/intent/src/commands/list.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { detectIntentAudience } from '../shared/environment.js'
import { formatIntentCommand } from '../shared/command-runner.js'
import { listIntentSkills } from '../core/index.js'
import {
coreOptionsFromGlobalFlags,
noticeOptionsFromGlobalFlags,
printDebugInfo,
printNotices,
printWarnings,
} from './support.js'
import { detectIntentAudience } from '../shared/environment.js'
import { formatIntentCommand } from '../shared/command-runner.js'
import { listIntentSkills } from '../core/index.js'
import type { GlobalScanFlags } from './support.js'
import type {
IntentPackageSummary,
Expand Down
4 changes: 2 additions & 2 deletions packages/intent/src/commands/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ function printLoadDebug(loaded: LoadedIntentSkill | ResolvedIntentSkill): void {
])
}

export async function runLoadCommand(
export function runLoadCommand(
use: string | undefined,
options: LoadCommandOptions,
): Promise<void> {
): void {
if (!use) {
fail('Missing skill use. Expected: intent load <package>#<skill>')
}
Expand Down
4 changes: 2 additions & 2 deletions packages/intent/src/core/source-policy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { scanForIntents } from '../discovery/scanner.js'
import { detectIntentAudience } from '../shared/environment.js'
import { ALLOW_ALL_NOTICE } from '../shared/cli-output.js'
import {
compileExcludePatterns,
getConfigDirs,
Expand All @@ -22,8 +23,7 @@ import type {
IntentHiddenSourceSummary,
} from './types.js'

export const ALLOW_ALL_NOTICE =
'All skill sources allowed (intent.skills: ["*"]) — unvetted skills may be surfaced into agent guidance.'
export { ALLOW_ALL_NOTICE }

export const MIGRATION_NOTICE =
'intent.skills is not set — all discovered skill sources are surfaced. A future version will require an explicit intent.skills allowlist; add one to opt in to specific sources.'
Expand Down
2 changes: 1 addition & 1 deletion packages/intent/src/hooks/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ function isIntentHook(value: unknown): boolean {
}

function isIntentGateScriptReference(value: string): boolean {
return /(?:^|[\s"'\/])(?:old-)?intent-(claude|codex|copilot)-gate\.mjs(?:$|[?#\s"'])/i.test(
return /(?:^|[\s"'/])(?:old-)?intent-(claude|codex|copilot)-gate\.mjs(?:$|[?#\s"'])/i.test(
value,
)
}
Expand Down
16 changes: 14 additions & 2 deletions packages/intent/src/shared/cli-output.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
// Lives here (not core/source-policy.ts) so printNotices can enforce
// non-suppressibility by identity without core importing this module.
export const ALLOW_ALL_NOTICE =
'All skill sources allowed (intent.skills: ["*"]) — unvetted skills may be surfaced into agent guidance.'

export function printWarnings(warnings: Array<string>): void {
if (warnings.length === 0) return

Expand Down Expand Up @@ -27,10 +32,17 @@ export function printNotices(
options: NoticeOutputOptions = {},
): void {
if (notices.length === 0) return
if (shouldSuppressNotices(options)) return

// ALLOW_ALL_NOTICE stays visible even when suppressed: agent hooks read
// warnings/conflicts but never notices, so keeping it here (rather than in
// warnings) also keeps it out of agent-injected context automatically.
const visible = shouldSuppressNotices(options)
? notices.filter((notice) => notice === ALLOW_ALL_NOTICE)
: notices
if (visible.length === 0) return

console.error('Notices:')
for (const notice of notices) {
for (const notice of visible) {
console.error(` ℹ ${notice}`)
}
}
2 changes: 1 addition & 1 deletion packages/intent/src/staleness/check.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { existsSync, readFileSync } from 'node:fs'
import { isAbsolute, join, relative, resolve } from 'node:path'
import semver from 'semver'
import { readIntentArtifacts } from './artifact-coverage.js'
import {
findSkillFiles,
parseFrontmatter,
readScalarField,
toPosixPath,
} from '../shared/utils.js'
import { readIntentArtifacts } from './artifact-coverage.js'
import type {
IntentArtifactSet,
IntentArtifactSkill,
Expand Down
54 changes: 43 additions & 11 deletions packages/intent/tests/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1108,6 +1108,38 @@ describe('cli commands', () => {
expect(stderr).not.toContain('Notices:')
})

it('does not suppress the allow-all risk banner under --no-notices', async () => {
const root = mkdtempSync(
join(realTmpdir, 'intent-cli-list-allow-all-no-notices-'),
)
const isolatedGlobalRoot = mkdtempSync(
join(realTmpdir, 'intent-cli-list-allow-all-no-notices-empty-global-'),
)
tempDirs.push(root, isolatedGlobalRoot)
writeJson(join(root, 'package.json'), {
name: 'consumer',
intent: { skills: ['*'] },
})
writeInstalledIntentPackage(root, {
name: '@tanstack/query',
version: '5.0.0',
skillName: 'fetching',
description: 'Query data fetching patterns',
})

process.env.INTENT_GLOBAL_NODE_MODULES = isolatedGlobalRoot
process.chdir(root)

const exitCode = await main(['list', '--no-notices'])
const stdout = logSpy.mock.calls.flat().join('\n')
const stderr = errorSpy.mock.calls.flat().join('\n')

expect(exitCode).toBe(0)
expect(stdout).toContain('@tanstack/query')
expect(stderr).toContain('Notices:')
expect(stderr).toContain('All skill sources allowed')
})

it('suppresses notices when INTENT_NO_NOTICES=1 is set', async () => {
const root = mkdtempSync(
join(realTmpdir, 'intent-cli-list-env-no-notices-'),
Expand Down Expand Up @@ -2728,7 +2760,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(root)
Expand Down Expand Up @@ -2781,7 +2813,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(root)
Expand Down Expand Up @@ -2849,7 +2881,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(root)
Expand Down Expand Up @@ -2916,7 +2948,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(root)
Expand Down Expand Up @@ -2986,7 +3018,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(root)
Expand Down Expand Up @@ -3044,7 +3076,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(root)
Expand Down Expand Up @@ -3133,7 +3165,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.env.INTENT_GLOBAL_NODE_MODULES = globalRoot
Expand Down Expand Up @@ -3177,7 +3209,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(root)
Expand Down Expand Up @@ -3223,7 +3255,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(root)
Expand Down Expand Up @@ -3267,7 +3299,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

process.chdir(join(root, 'packages', 'router'))
Expand Down Expand Up @@ -3300,7 +3332,7 @@ describe('cli commands', () => {

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)

const elsewhere = mkdtempSync(join(realTmpdir, 'intent-cli-stale-abs-cwd-'))
Expand Down
2 changes: 1 addition & 1 deletion packages/intent/tests/hooks-install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,7 +379,7 @@ describe('hook installer', () => {
})

expect(result.status).toBe(0)
const output = JSON.parse(result.stdout) as any
const output = JSON.parse(result.stdout)
const context =
agent === 'copilot'
? output.additionalContext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ describe('source policy — all four surfaces filter excluded and unlisted', ()

const fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue({
ok: true,
json: async () => ({ version: '1.0.0' }),
json: () => Promise.resolve({ version: '1.0.0' }),
} as Response)
process.chdir(root)

Expand Down
Loading
Loading